diff --git a/ballisticacore_server b/ballisticacore_server deleted file mode 100644 index dad8792..0000000 --- a/ballisticacore_server +++ /dev/null @@ -1,901 +0,0 @@ -#!/usr/bin/env -S python3.10 -O -# Released under the MIT License. See LICENSE for details. -# -"""BallisticaCore server manager.""" -from __future__ import annotations - -import json -import os -import signal -import subprocess -import sys -import time -from pathlib import Path -from threading import Lock, Thread, current_thread -from typing import TYPE_CHECKING -from nbstreamreader import NonBlockingStreamReader as NBSR -import _thread -ERROR_LOGGING=False - -# We make use of the bacommon and efro packages as well as site-packages -# included with our bundled Ballistica dist, so we need to add those paths -# before we import them. -sys.path += [ - str(Path(Path(__file__).parent, 'dist', 'ba_data', 'python')), - str(Path(Path(__file__).parent, 'dist', 'ba_data', 'python-site-packages')) -] - -from bacommon.servermanager import ServerConfig, StartServerModeCommand -from efro.dataclassio import dataclass_from_dict, dataclass_validate -from efro.error import CleanError -from efro.terminal import Clr - -if TYPE_CHECKING: - from types import FrameType - from bacommon.servermanager import ServerCommand - -VERSION_STR = '1.3' - -# Version history: -# 1.3.1 -# Windows binary is now named BallisticaCoreHeadless.exe -# 1.3: -# Added show_tutorial config option -# Added team_names config option -# Added team_colors config option -# Added playlist_inline config option -# 1.2: -# Added optional --help arg -# Added --config arg for specifying config file and --root for ba_root path -# Added noninteractive mode and --interactive/--noninteractive args to -# explicitly enable/disable it (it is autodetected by default) -# Added explicit control for auto-restart: --no-auto-restart -# Config file is now reloaded each time server binary is restarted; no more -# need to bring down server wrapper to pick up changes -# Now automatically restarts server binary when config file is modified -# (use --no-config-auto-restart to disable that behavior) -# 1.1.1: -# Switched config reading to use efro.dataclasses.dataclass_from_dict() -# 1.1.0: -# Added shutdown command -# Changed restart to default to immediate=True -# Added clean_exit_minutes, unclean_exit_minutes, and idle_exit_minutes -# 1.0.0: -# Initial release - - -class ServerManagerApp: - """An app which manages BallisticaCore server execution. - - Handles configuring, launching, re-launching, and otherwise - managing BallisticaCore operating in server mode. - """ - - # How many seconds we wait after asking our subprocess to do an immediate - # shutdown before bringing down the hammer. - IMMEDIATE_SHUTDOWN_TIME_LIMIT = 5.0 - - def __init__(self) -> None: - self._config_path = 'config.yaml' - self._user_provided_config_path = False - self._config = ServerConfig() - self._ba_root_path = os.path.abspath('dist/ba_root') - self._interactive = sys.stdin.isatty() - self._wrapper_shutdown_desired = False - self._done = False - self._subprocess_commands: list[str | ServerCommand] = [] - self._subprocess_commands_lock = Lock() - self._subprocess_force_kill_time: float | None = None - self._auto_restart = True - self._config_auto_restart = True - self._config_mtime: float | None = None - self._last_config_mtime_check_time: float | None = None - self._should_report_subprocess_error = False - self._running = False - self._interpreter_start_time: float | None = None - self._subprocess: subprocess.Popen[bytes] | None = None - self._subprocess_launch_time: float | None = None - self._subprocess_sent_config_auto_restart = False - self._subprocess_sent_clean_exit = False - self._subprocess_sent_unclean_exit = False - self._subprocess_thread: Thread | None = None - self._subprocess_exited_cleanly: bool | None = None - self.nbsr = None - - # This may override the above defaults. - self._parse_command_line_args() - - # Do an initial config-load. If the config is invalid at this point - # we can cleanly die (we're more lenient later on reloads). - self.load_config(strict=True, print_confirmation=False) - - @property - def config(self) -> ServerConfig: - """The current config for the app.""" - return self._config - - @config.setter - def config(self, value: ServerConfig) -> None: - dataclass_validate(value) - self._config = value - - def _prerun(self) -> None: - """Common code at the start of any run.""" - - # Make sure we don't call run multiple times. - if self._running: - raise RuntimeError('Already running.') - self._running = True - - dbgstr = 'debug' if __debug__ else 'opt' - print( - f'{Clr.CYN}{Clr.BLD}BallisticaCore server manager {VERSION_STR}' - f' starting up ({dbgstr} mode)...{Clr.RST}', - flush=True) - - # Python will handle SIGINT for us (as KeyboardInterrupt) but we - # need to register a SIGTERM handler so we have a chance to clean - # up our subprocess when someone tells us to die. (and avoid - # zombie processes) - signal.signal(signal.SIGTERM, self._handle_term_signal) - - # During a run, we make the assumption that cwd is the dir - # containing this script, so make that so. Up until now that may - # not be the case (we support being called from any location). - os.chdir(os.path.abspath(os.path.dirname(__file__))) - - # Fire off a background thread to wrangle our server binaries. - self._subprocess_thread = Thread(target=self._bg_thread_main) - self._subprocess_thread.start() - - def _postrun(self) -> None: - """Common code at the end of any run.""" - print(f'{Clr.CYN}Server manager shutting down...{Clr.RST}', flush=True) - - assert self._subprocess_thread is not None - if self._subprocess_thread.is_alive(): - print(f'{Clr.CYN}Waiting for subprocess exit...{Clr.RST}', - flush=True) - - # Mark ourselves as shutting down and wait for the process to wrap up. - self._done = True - self._subprocess_thread.join() - - # If there's a server error we should care about, exit the - # entire wrapper uncleanly. - if self._should_report_subprocess_error: - raise CleanError('Server subprocess exited uncleanly.') - - def run(self) -> None: - """Do the thing.""" - if self._interactive: - self._run_interactive() - else: - self._run_noninteractive() - - def _run_noninteractive(self) -> None: - """Run the app loop to completion noninteractively.""" - self._prerun() - try: - while True: - time.sleep(1.234) - except KeyboardInterrupt: - # Gracefully bow out if we kill ourself via keyboard. - pass - except SystemExit: - # We get this from the builtin quit(), our signal handler, etc. - # Need to catch this so we can clean up, otherwise we'll be - # left in limbo with our process thread still running. - pass - self._postrun() - - def _run_interactive(self) -> None: - """Run the app loop to completion interactively.""" - import code - self._prerun() - - # Print basic usage info for interactive mode. - print( - f"{Clr.CYN}Interactive mode enabled; use the 'mgr' object" - f' to interact with the server.\n' - f"Type 'help(mgr)' for more information.{Clr.RST}", - flush=True) - - context = {'__name__': '__console__', '__doc__': None, 'mgr': self} - - # Enable tab-completion if possible. - self._enable_tab_completion(context) - - # Now just sit in an interpreter. - # TODO: make it possible to use IPython if the user has it available. - try: - self._interpreter_start_time = time.time() - code.interact(local=context, banner='', exitmsg='') - except SystemExit: - # We get this from the builtin quit(), our signal handler, etc. - # Need to catch this so we can clean up, otherwise we'll be - # left in limbo with our process thread still running. - pass - except BaseException as exc: - print( - f'{Clr.SRED}Unexpected interpreter exception:' - f' {exc} ({type(exc)}){Clr.RST}', - flush=True) - - self._postrun() - - def cmd(self, statement: str) -> None: - """Exec a Python command on the current running server subprocess. - - Note that commands are executed asynchronously and no status or - return value is accessible from this manager app. - """ - if not isinstance(statement, str): - raise TypeError(f'Expected a string arg; got {type(statement)}') - with self._subprocess_commands_lock: - self._subprocess_commands.append(statement) - self._block_for_command_completion() - - def _block_for_command_completion(self) -> None: - # Ideally we'd block here until the command was run so our prompt would - # print after it's results. We currently don't get any response from - # the app so the best we can do is block until our bg thread has sent - # it. In the future we can perhaps add a proper 'command port' - # interface for proper blocking two way communication. - while True: - with self._subprocess_commands_lock: - if not self._subprocess_commands: - break - time.sleep(0.1) - - # One last short delay so if we come out *just* as the command is sent - # we'll hopefully still give it enough time to process/print. - time.sleep(0.1) - - def screenmessage(self, - message: str, - color: tuple[float, float, float] | None = None, - clients: list[int] | None = None) -> None: - """Display a screen-message. - - This will have no name attached and not show up in chat history. - They will show up in replays, however (unless clients is passed). - """ - from bacommon.servermanager import ScreenMessageCommand - self._enqueue_server_command( - ScreenMessageCommand(message=message, color=color, - clients=clients)) - - def chatmessage(self, - message: str, - clients: list[int] | None = None) -> None: - """Send a chat message from the server. - - This will have the server's name attached and will be logged - in client chat windows, just like other chat messages. - """ - from bacommon.servermanager import ChatMessageCommand - self._enqueue_server_command( - ChatMessageCommand(message=message, clients=clients)) - - def clientlist(self) -> None: - """Print a list of connected clients.""" - from bacommon.servermanager import ClientListCommand - self._enqueue_server_command(ClientListCommand()) - self._block_for_command_completion() - - def kick(self, client_id: int, ban_time: int | None = None) -> None: - """Kick the client with the provided id. - - If ban_time is provided, the client will be banned for that - length of time in seconds. If it is None, ban duration will - be determined automatically. Pass 0 or a negative number for no - ban time. - """ - from bacommon.servermanager import KickCommand - self._enqueue_server_command( - KickCommand(client_id=client_id, ban_time=ban_time)) - - def restart(self, immediate: bool = True) -> None: - """Restart the server subprocess. - - By default, the current server process will exit immediately. - If 'immediate' is passed as False, however, it will instead exit at - the next clean transition point (the end of a series, etc). - """ - from bacommon.servermanager import ShutdownCommand, ShutdownReason - self._enqueue_server_command( - ShutdownCommand(reason=ShutdownReason.RESTARTING, - immediate=immediate)) - - # If we're asking for an immediate restart but don't get one within - # the grace period, bring down the hammer. - if immediate: - self._subprocess_force_kill_time = ( - time.time() + self.IMMEDIATE_SHUTDOWN_TIME_LIMIT) - - def shutdown(self, immediate: bool = True) -> None: - """Shut down the server subprocess and exit the wrapper. - - By default, the current server process will exit immediately. - If 'immediate' is passed as False, however, it will instead exit at - the next clean transition point (the end of a series, etc). - """ - from bacommon.servermanager import ShutdownCommand, ShutdownReason - self._enqueue_server_command( - ShutdownCommand(reason=ShutdownReason.NONE, immediate=immediate)) - - # An explicit shutdown means we know to bail completely once this - # subprocess completes. - self._wrapper_shutdown_desired = True - - # If we're asking for an immediate shutdown but don't get one within - # the grace period, bring down the hammer. - if immediate: - self._subprocess_force_kill_time = ( - time.time() + self.IMMEDIATE_SHUTDOWN_TIME_LIMIT) - - def _parse_command_line_args(self) -> None: - """Parse command line args.""" - # pylint: disable=too-many-branches - - i = 1 - argc = len(sys.argv) - did_set_interactive = False - while i < argc: - arg = sys.argv[i] - if arg == '--help': - self.print_help() - sys.exit(0) - elif arg == '--config': - if i + 1 >= argc: - raise CleanError('Expected a config path as next arg.') - path = sys.argv[i + 1] - if not os.path.exists(path): - raise CleanError( - f"Supplied path does not exist: '{path}'.") - # We need an abs path because we may be in a different - # cwd currently than we will be during the run. - self._config_path = os.path.abspath(path) - self._user_provided_config_path = True - i += 2 - elif arg == '--root': - if i + 1 >= argc: - raise CleanError('Expected a path as next arg.') - path = sys.argv[i + 1] - # Unlike config_path, this one doesn't have to exist now. - # We do however need an abs path because we may be in a - # different cwd currently than we will be during the run. - self._ba_root_path = os.path.abspath(path) - i += 2 - elif arg == '--interactive': - if did_set_interactive: - raise CleanError('interactive/noninteractive can only' - ' be specified once.') - self._interactive = True - did_set_interactive = True - i += 1 - elif arg == '--noninteractive': - if did_set_interactive: - raise CleanError('interactive/noninteractive can only' - ' be specified once.') - self._interactive = False - did_set_interactive = True - i += 1 - elif arg == '--no-auto-restart': - self._auto_restart = False - i += 1 - elif arg == '--no-config-auto-restart': - self._config_auto_restart = False - i += 1 - else: - raise CleanError(f"Invalid arg: '{arg}'.") - - @classmethod - def _par(cls, txt: str) -> str: - """Spit out a pretty paragraph for our help text.""" - import textwrap - ind = ' ' * 2 - out = textwrap.fill(txt, 80, initial_indent=ind, subsequent_indent=ind) - return f'{out}\n' - - @classmethod - def print_help(cls) -> None: - """Print app help.""" - filename = os.path.basename(__file__) - out = ( - f'{Clr.BLD}{filename} usage:{Clr.RST}\n' + cls._par( - 'This script handles configuring, launching, re-launching,' - ' and otherwise managing BallisticaCore operating' - ' in server mode. It can be run with no arguments, but' - ' accepts the following optional ones:') + f'\n' - f'{Clr.BLD}--help:{Clr.RST}\n' - f' Show this help.\n' - f'\n' - f'{Clr.BLD}--config [path]{Clr.RST}\n' + cls._par( - 'Set the config file read by the server script. The config' - ' file contains most options for what kind of game to host.' - ' It should be in yaml format. Note that yaml is backwards' - ' compatible with json so you can just write json if you' - ' want to. If not specified, the script will look for a' - ' file named \'config.yaml\' in the same directory as the' - ' script.') + '\n' - f'{Clr.BLD}--root [path]{Clr.RST}\n' + cls._par( - 'Set the ballistica root directory. This is where the server' - ' binary will read and write its caches, state files,' - ' downloaded assets to, etc. It needs to be a writable' - ' directory. If not specified, the script will use the' - ' \'dist/ba_root\' directory relative to itself.') + '\n' - f'{Clr.BLD}--interactive{Clr.RST}\n' - f'{Clr.BLD}--noninteractive{Clr.RST}\n' + cls._par( - 'Specify whether the script should run interactively.' - ' In interactive mode, the script creates a Python interpreter' - ' and reads commands from stdin, allowing for live interaction' - ' with the server. The server script will then exit when ' - 'end-of-file is reached in stdin. Noninteractive mode creates' - ' no interpreter and is more suited to being run in automated' - ' scenarios. By default, interactive mode will be used if' - ' a terminal is detected and noninteractive mode otherwise.') + - '\n' - f'{Clr.BLD}--no-auto-restart{Clr.RST}\n' + - cls._par('Auto-restart is enabled by default, which means the' - ' server manager will restart the server binary whenever' - ' it exits (even when uncleanly). Disabling auto-restart' - ' will cause the server manager to instead exit after a' - ' single run and also to return error codes if the' - ' server binary did so.') + '\n' - f'{Clr.BLD}--no-config-auto-restart{Clr.RST}\n' + cls._par( - 'By default, when auto-restart is enabled, the server binary' - ' will be automatically restarted if changes to the server' - ' config file are detected. This disables that behavior.')) - print(out) - - def load_config(self, strict: bool, print_confirmation: bool) -> None: - """Load the config. - - If strict is True, errors will propagate upward. - Otherwise, warnings will be printed and repeated attempts will be - made to load the config. Eventually the function will give up - and leave the existing config as-is. - """ - retry_seconds = 3 - maxtries = 11 - for trynum in range(maxtries): - try: - self._config = self._load_config_from_file( - print_confirmation=print_confirmation) - return - except Exception as exc: - if strict: - raise CleanError( - f'Error loading config file:\n{exc}') from exc - print(f'{Clr.RED}Error loading config file:\n{exc}.{Clr.RST}', - flush=True) - if trynum == maxtries - 1: - print( - f'{Clr.RED}Max-tries reached; giving up.' - f' Existing config values will be used.{Clr.RST}', - flush=True) - break - print( - f'{Clr.CYN}Please correct the error.' - f' Will re-attempt load in {retry_seconds}' - f' seconds. (attempt {trynum+1} of' - f' {maxtries-1}).{Clr.RST}', - flush=True) - - for _j in range(retry_seconds): - # If the app is trying to die, drop what we're doing. - if self._done: - return - time.sleep(1) - - def _load_config_from_file(self, print_confirmation: bool) -> ServerConfig: - - out: ServerConfig | None = None - - if not os.path.exists(self._config_path): - - # Special case: - # If the user didn't specify a particular config file, allow - # gracefully falling back to defaults if the default one is - # missing. - if not self._user_provided_config_path: - if print_confirmation: - print( - f'{Clr.YLW}Default config file not found' - f' (\'{self._config_path}\'); using default' - f' settings.{Clr.RST}', - flush=True) - self._config_mtime = None - self._last_config_mtime_check_time = time.time() - return ServerConfig() - - # Don't be so lenient if the user pointed us at one though. - raise RuntimeError( - f"Config file not found: '{self._config_path}'.") - - import yaml - with open(self._config_path, encoding='utf-8') as infile: - user_config_raw = yaml.safe_load(infile.read()) - - # An empty config file will yield None, and that's ok. - if user_config_raw is not None: - out = dataclass_from_dict(ServerConfig, user_config_raw) - - # Update our known mod-time since we know it exists. - self._config_mtime = Path(self._config_path).stat().st_mtime - self._last_config_mtime_check_time = time.time() - - # Go with defaults if we weren't able to load anything. - if out is None: - out = ServerConfig() - - if print_confirmation: - print(f'{Clr.CYN}Valid server config file loaded.{Clr.RST}', - flush=True) - return out - - def _enable_tab_completion(self, locs: dict) -> None: - """Enable tab-completion on platforms where available (linux/mac).""" - try: - import readline - import rlcompleter - readline.set_completer(rlcompleter.Completer(locs).complete) - readline.parse_and_bind('tab:complete') - except ImportError: - # This is expected (readline doesn't exist under windows). - pass - - def _bg_thread_main(self) -> None: - """Top level method run by our bg thread.""" - while not self._done: - self._run_server_cycle() - - def _handle_term_signal(self, sig: int, frame: FrameType | None) -> None: - """Handle signals (will always run in the main thread).""" - del sig, frame # Unused. - sys.exit(1 if self._should_report_subprocess_error else 0) - - def _run_server_cycle(self) -> None: - """Spin up the server subprocess and run it until exit.""" - # pylint: disable=consider-using-with - - # Reload our config, and update our overall behavior based on it. - # We do non-strict this time to give the user repeated attempts if - # if they mess up while modifying the config on the fly. - self.load_config(strict=False, print_confirmation=True) - - self._prep_subprocess_environment() - - # Launch the binary and grab its stdin; - # we'll use this to feed it commands. - self._subprocess_launch_time = time.time() - - # Set an environment var so the server process knows its being - # run under us. This causes it to ignore ctrl-c presses and other - # slight behavior tweaks. Hmm; should this be an argument instead? - os.environ['BA_SERVER_WRAPPER_MANAGED'] = '1' - - print(f'{Clr.CYN}Launching server subprocess...{Clr.RST}', flush=True) - binary_name = ('BallisticaCoreHeadless.exe' - if os.name == 'nt' else './bombsquad_headless') - assert self._ba_root_path is not None - self._subprocess = None - - # Launch! - try: - if ERROR_LOGGING: - self._subprocess = subprocess.Popen( - [binary_name, '-cfgdir', self._ba_root_path], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - cwd='dist') - - self.nbsr = NBSR(self._subprocess.stdout) - self.nbsrerr = NBSR(self._subprocess.stderr) - else: - self._subprocess = subprocess.Popen( - [binary_name, '-cfgdir', self._ba_root_path], - stdin=subprocess.PIPE, - cwd='dist') - except Exception as exc: - self._subprocess_exited_cleanly = False - print( - f'{Clr.RED}Error launching server subprocess: {exc}{Clr.RST}', - flush=True) - - # Do the thing. - try: - self._run_subprocess_until_exit() - except Exception as exc: - print(f'{Clr.RED}Error running server subprocess: {exc}{Clr.RST}', - flush=True) - - self._kill_subprocess() - - assert self._subprocess_exited_cleanly is not None - - # EW: it seems that if we die before the main thread has fully started - # up the interpreter, its possible that it will not break out of its - # loop via the usual SystemExit that gets sent when we die. - if self._interactive: - while (self._interpreter_start_time is None - or time.time() - self._interpreter_start_time < 0.5): - time.sleep(0.1) - - # Avoid super fast death loops. - if (not self._subprocess_exited_cleanly and self._auto_restart - and not self._done): - time.sleep(5.0) - - # If they don't want auto-restart, we'll exit the whole wrapper. - # (and with an error code if things ended badly). - if not self._auto_restart: - self._wrapper_shutdown_desired = True - if not self._subprocess_exited_cleanly: - self._should_report_subprocess_error = True - - self._reset_subprocess_vars() - - # If we want to die completely after this subprocess has ended, - # tell the main thread to die. - if self._wrapper_shutdown_desired: - - # Only do this if the main thread is not already waiting for - # us to die; otherwise it can lead to deadlock. - # (we hang in os.kill while main thread is blocked in Thread.join) - if not self._done: - self._done = True - - # This should break the main thread out of its blocking - # interpreter call. - os.kill(os.getpid(), signal.SIGTERM) - - def _prep_subprocess_environment(self) -> None: - """Write files that must exist at process launch.""" - - assert self._ba_root_path is not None - os.makedirs(self._ba_root_path, exist_ok=True) - cfgpath = os.path.join(self._ba_root_path, 'config.json') - if os.path.exists(cfgpath): - with open(cfgpath, encoding='utf-8') as infile: - bincfg = json.loads(infile.read()) - else: - bincfg = {} - - # Some of our config values translate directly into the - # ballisticacore config file; the rest we pass at runtime. - bincfg['Port'] = self._config.port - bincfg['Auto Balance Teams'] = self._config.auto_balance_teams - bincfg['Show Tutorial'] = self._config.show_tutorial - - if self._config.team_names is not None: - bincfg['Custom Team Names'] = self._config.team_names - elif 'Custom Team Names' in bincfg: - del bincfg['Custom Team Names'] - - if self._config.team_colors is not None: - bincfg['Custom Team Colors'] = self._config.team_colors - elif 'Custom Team Colors' in bincfg: - del bincfg['Custom Team Colors'] - - bincfg['Idle Exit Minutes'] = self._config.idle_exit_minutes - with open(cfgpath, 'w', encoding='utf-8') as outfile: - outfile.write(json.dumps(bincfg)) - - def _enqueue_server_command(self, command: ServerCommand) -> None: - """Enqueue a command to be sent to the server. - - Can be called from any thread. - """ - with self._subprocess_commands_lock: - self._subprocess_commands.append(command) - - def _send_server_command(self, command: ServerCommand) -> None: - """Send a command to the server. - - Must be called from the server process thread. - """ - import pickle - assert current_thread() is self._subprocess_thread - assert self._subprocess is not None - assert self._subprocess.stdin is not None - val = repr(pickle.dumps(command)) - assert '\n' not in val - execcode = (f'import ba._servermode;' - f' ba._servermode._cmd({val})\n').encode() - self._subprocess.stdin.write(execcode) - self._subprocess.stdin.flush() - - def _run_subprocess_until_exit(self) -> None: - if self._subprocess is None: - return - - assert current_thread() is self._subprocess_thread - assert self._subprocess.stdin is not None - - # Send the initial server config which should kick things off. - # (but make sure its values are still valid first) - dataclass_validate(self._config) - self._send_server_command(StartServerModeCommand(self._config)) - - while True: - - # If the app is trying to shut down, nope out immediately. - if self._done: - break - if ERROR_LOGGING: - out = self.nbsr.readline(0.1) - out2 = self.nbsrerr.readline(0.1) - if out: - sys.stdout.write(out.decode("utf-8")) - _thread.start_new_thread(dump_logs, (out.decode("utf-8"),)) - if out2: - sys.stdout.write(out2.decode("utf-8")) - _thread.start_new_thread(dump_logs, (out2.decode("utf-8"),)) - # Pass along any commands to our process. - with self._subprocess_commands_lock: - for incmd in self._subprocess_commands: - # If we're passing a raw string to exec, no need to wrap it - # in any proper structure. - if isinstance(incmd, str): - self._subprocess.stdin.write((incmd + '\n').encode()) - self._subprocess.stdin.flush() - else: - self._send_server_command(incmd) - self._subprocess_commands = [] - - # Request restarts/shut-downs for various reasons. - self._request_shutdowns_or_restarts() - - # If they want to force-kill our subprocess, simply exit this - # loop; the cleanup code will kill the process if its still - # alive. - if (self._subprocess_force_kill_time is not None - and time.time() > self._subprocess_force_kill_time): - print( - f'{Clr.CYN}Immediate shutdown time limit' - f' ({self.IMMEDIATE_SHUTDOWN_TIME_LIMIT:.1f} seconds)' - f' expired; force-killing subprocess...{Clr.RST}', - flush=True) - break - - # Watch for the server process exiting.. - code: int | None = self._subprocess.poll() - if code is not None: - - clr = Clr.CYN if code == 0 else Clr.RED - print( - f'{clr}Server subprocess exited' - f' with code {code}.{Clr.RST}', - flush=True) - self._subprocess_exited_cleanly = (code == 0) - break - - time.sleep(0.25) - - def _request_shutdowns_or_restarts(self) -> None: - # pylint: disable=too-many-branches - assert current_thread() is self._subprocess_thread - assert self._subprocess_launch_time is not None - now = time.time() - minutes_since_launch = (now - self._subprocess_launch_time) / 60.0 - - # If we're doing auto-restart with config changes, handle that. - if (self._auto_restart and self._config_auto_restart - and not self._subprocess_sent_config_auto_restart): - if (self._last_config_mtime_check_time is None - or (now - self._last_config_mtime_check_time) > 3.123): - self._last_config_mtime_check_time = now - mtime: float | None - if os.path.isfile(self._config_path): - mtime = Path(self._config_path).stat().st_mtime - else: - mtime = None - if mtime != self._config_mtime: - print( - f'{Clr.CYN}Config-file change detected;' - f' requesting immediate restart.{Clr.RST}', - flush=True) - self.restart(immediate=True) - self._subprocess_sent_config_auto_restart = True - - # Attempt clean exit if our clean-exit-time passes. - # (and enforce a 6 hour max if not provided) - clean_exit_minutes = 360.0 - if self._config.clean_exit_minutes is not None: - clean_exit_minutes = min(clean_exit_minutes, - self._config.clean_exit_minutes) - if clean_exit_minutes is not None: - if (minutes_since_launch > clean_exit_minutes - and not self._subprocess_sent_clean_exit): - opname = 'restart' if self._auto_restart else 'shutdown' - print( - f'{Clr.CYN}clean_exit_minutes' - f' ({clean_exit_minutes})' - f' elapsed; requesting soft' - f' {opname}.{Clr.RST}', - flush=True) - if self._auto_restart: - self.restart(immediate=False) - else: - self.shutdown(immediate=False) - self._subprocess_sent_clean_exit = True - - # Attempt unclean exit if our unclean-exit-time passes. - # (and enforce a 7 hour max if not provided) - unclean_exit_minutes = 420.0 - if self._config.unclean_exit_minutes is not None: - unclean_exit_minutes = min(unclean_exit_minutes, - self._config.unclean_exit_minutes) - if unclean_exit_minutes is not None: - if (minutes_since_launch > unclean_exit_minutes - and not self._subprocess_sent_unclean_exit): - opname = 'restart' if self._auto_restart else 'shutdown' - print( - f'{Clr.CYN}unclean_exit_minutes' - f' ({unclean_exit_minutes})' - f' elapsed; requesting immediate' - f' {opname}.{Clr.RST}', - flush=True) - if self._auto_restart: - self.restart(immediate=True) - else: - self.shutdown(immediate=True) - self._subprocess_sent_unclean_exit = True - - def _reset_subprocess_vars(self) -> None: - self._subprocess = None - self._subprocess_launch_time = None - self._subprocess_sent_config_auto_restart = False - self._subprocess_sent_clean_exit = False - self._subprocess_sent_unclean_exit = False - self._subprocess_force_kill_time = None - self._subprocess_exited_cleanly = None - - def _kill_subprocess(self) -> None: - """End the server subprocess if it still exists.""" - assert current_thread() is self._subprocess_thread - if self._subprocess is None: - return - - print(f'{Clr.CYN}Stopping subprocess...{Clr.RST}', flush=True) - - # First, ask it nicely to die and give it a moment. - # If that doesn't work, bring down the hammer. - self._subprocess.terminate() - try: - self._subprocess.wait(timeout=10) - self._subprocess_exited_cleanly = ( - self._subprocess.returncode == 0) - except subprocess.TimeoutExpired: - self._subprocess_exited_cleanly = False - self._subprocess.kill() - print(f'{Clr.CYN}Subprocess stopped.{Clr.RST}', flush=True) - - -def main() -> None: - """Run the BallisticaCore server manager.""" - try: - ServerManagerApp().run() - except CleanError as exc: - # For clean errors, do a simple print and fail; no tracebacks/etc. - # Any others will bubble up and give us the usual mess. - exc.pretty_print() - sys.exit(1) - -def dump_logs(msg): - if os.path.isfile('logs.log'): - size = os.path.getsize('logs.log') - - if size > 2000000: - os.remove('logs.log') - - with open("logs.log", "a") as f: - f.write(msg) - - -if __name__ == '__main__': - main() diff --git a/bombsquad_server b/bombsquad_server index 70d9896..450a2ca 100644 --- a/bombsquad_server +++ b/bombsquad_server @@ -607,7 +607,7 @@ class ServerManagerApp: # run under us. This causes it to ignore ctrl-c presses and other # slight behavior tweaks. Hmm; should this be an argument instead? os.environ['BA_SERVER_WRAPPER_MANAGED'] = '1' - + os.environ['BA_HOST_NAME'] = self._config.party_name print(f'{Clr.CYN}Launching server subprocess...{Clr.RST}', flush=True) binary_name = ('BallisticaCoreHeadless.exe' if os.name == 'nt' else './bombsquad_headless') diff --git a/dist/ba_data/data/langdata.json b/dist/ba_data/data/langdata.json index 99b4c6b..3798c75 100644 --- a/dist/ba_data/data/langdata.json +++ b/dist/ba_data/data/langdata.json @@ -78,7 +78,7 @@ "adan", "Adeel (AdeZ {@adez_})", "Adel", - "Rio adi", + "Rio Adi", "Rayhan Adiansyah", "Yonas Adiel", "admin", @@ -159,6 +159,7 @@ "amr", "Anandchaursiya", "Anas", + "Anastasija", "AnatoliyanChubukov", "AnatoliyanChybycov", "Bryan Enrique Perez de Anda", @@ -184,6 +185,7 @@ "Aniol", "Anmol", "anonymous", + "Alok. R. Anoop", "Antonio", "Antoniom", "Lucas Antunes", @@ -1107,7 +1109,7 @@ "Nikita Oleshko", "Omar", "On3GaMs", - "No one", + "No One", "Adam Oros", "Andrés Ortega", "Zangar Orynbetov", @@ -1292,6 +1294,7 @@ "Bsam bu salh", "M. Rizki Agus Salim", "Salted", + "Matteo Salvini", "Salvo04", "San", "SaNt0RiNiKits577YT", @@ -1347,6 +1350,7 @@ "Nikhil sohan", "SoK", "SoldierBS", + "Unnamed Solicitude", "SPT Sosat", "Soto", "spacechase26", @@ -1485,6 +1489,7 @@ "vinoth", "Vishal", "Voxel", + "Voxel25", "VTOR", "Fernando Véliz", "Vít", @@ -1527,6 +1532,7 @@ "xxonx8", "Ajeet yadav", "yahya", + "Arda Yalın", "Yamir", "YannSonic", "Yantohrmnt401", @@ -1542,6 +1548,7 @@ "Yovan182Sunbreaker", "Yrtking", "All Star YT", + "Dark Fgg5 YT", "Yudhis", "yugo", "yullian", @@ -1594,6 +1601,7 @@ "Даниил", "данил", "дибисяра", + "Дмитрий 228", "Евгений(Eugene)", "Артём Зобков (KoLenka)", "Юстин Иглин", @@ -1705,6 +1713,7 @@ "药药Medic", "蔚蓝枫叶", "陈星宇你就是歌姬吧", + "随风飘姚", "鲨鱼服·Medic", "鲲鹏元帅", "꧁ℤephyro꧂", diff --git a/dist/ba_data/data/languages/chinese.json b/dist/ba_data/data/languages/chinese.json index 3530828..2f4d091 100644 --- a/dist/ba_data/data/languages/chinese.json +++ b/dist/ba_data/data/languages/chinese.json @@ -15,6 +15,7 @@ "linkAccountsInstructionsText": "若要关联两个账户,在其中一个账户内\n生成一个代码,用以在另一个账户内输入。\n游戏进程和物品将会被合并。\n您最多可以关联${COUNT}个账户\n\n重要:只能关联您自己的帐户!\n如果您跟您的朋友关联帐户\n您将无法在同一时间玩\n\n另外:此操作目前不能撤销,所以要小心!", "linkAccountsText": "关联账户", "linkedAccountsText": "已关联的账户:", + "manageAccountText": "管理账户", "nameChangeConfirm": "更改账户名称为${NAME}?", "notLoggedInText": "未登录", "resetProgressConfirmNoAchievementsText": "这样会重置单机模式的进程和\n本地的高分记录(并不包括你的点券),\n而且不能恢复,确定要这样做吗?", @@ -1009,6 +1010,7 @@ "creditsText": "制作团队", "demoMenuText": "演示菜单", "endGameText": "结束", + "endTestText": "结束测试", "exitGameText": "退出", "exitToMenuText": "退出到菜单", "howToPlayText": "帮助", diff --git a/dist/ba_data/data/languages/chinesetraditional.json b/dist/ba_data/data/languages/chinesetraditional.json index c7c1a69..6e470f2 100644 --- a/dist/ba_data/data/languages/chinesetraditional.json +++ b/dist/ba_data/data/languages/chinesetraditional.json @@ -13,6 +13,7 @@ "linkAccountsInstructionsNewText": "要連結兩個帳號,在第一個帳號里點擊“生成代碼”按鈕。\n在第二個帳號裡輸入那個代碼。\n兩個帳號數據將被共享。\n(第一個帳號的數據將會消失)\n\n您總共可連結${COUNT}個帳號。\n\n重要訊息:你只能連結你自己的帳號;\n如果你連結了朋友的帳號, \n你將無法同時線上遊玩。", "linkAccountsText": "連結帳號", "linkedAccountsText": "已連結的帳號:", + "manageAccountText": "管理賬戶", "nameChangeConfirm": "更改帳號名為${NAME}?", "resetProgressConfirmNoAchievementsText": "這會重設你的合作模式進度和\n本地高分紀錄 (但不包含你的票卷)。\n這是不可回復的操作,你確定嗎?", "resetProgressConfirmText": "這會重設你的合作模式進度,\n成就和本地高分\n(但不包括你的票卷)。這是不可\n回復的操作。你確定嗎?", @@ -495,6 +496,7 @@ "welcome2Text": "你還可以參加很多相同活動來贏取點券\n點券可以用於解鎖新角色、地圖、圖標\n和迷你遊戲,或購買錦標賽門票", "yourPowerRankingText": "你的能力排名:" }, + "copyConfirmText": "複製到剪切板", "copyOfText": "${NAME} 拷貝", "copyText": "複製", "createEditPlayerText": "<創建/編輯玩家>", @@ -995,6 +997,7 @@ "creditsText": "製作團隊", "demoMenuText": "演示菜單", "endGameText": "結束遊戲", + "endTestText": "結束測試", "exitGameText": "退出遊戲", "exitToMenuText": "退出到菜單?", "howToPlayText": "幫助", diff --git a/dist/ba_data/data/languages/czech.json b/dist/ba_data/data/languages/czech.json index 775db13..105fdde 100644 --- a/dist/ba_data/data/languages/czech.json +++ b/dist/ba_data/data/languages/czech.json @@ -15,6 +15,7 @@ "linkAccountsInstructionsText": "Pokud chcete propojit účty, na jednom z nich generujte kód\na na druhém tento kód zadejte.\nPostup a inventář bude zkombinován.\nMůžete propojit až ${COUNT} účtů.\n\nDŮLEŽITÉ: PROPOJUJTE POUZE ÚČTY KTERÉ JSOU VAŠE!\nPOKUD PROPOJÍTE ÚČTY S KAMARÁDEM, NEBUDETE MOCI\nHRÁT OBA VE STEJNÝ ČAS!\n\nDobře si to rozmyslete; Tuto akci nelze vrátit zpět!", "linkAccountsText": "Propojit účty", "linkedAccountsText": "Propojené účty:", + "manageAccountText": "Spravovat Účet", "nameChangeConfirm": "Přejete si změnit jméno účtu na ${NAME}?", "notLoggedInText": "", "resetProgressConfirmNoAchievementsText": "Jste si jistí, že to chcete udělat?\nTímto NENÁVRATNĚ resetujete veškerý postup Co-op\na lokální nejlepší výsledky (ale kupóny vám zůstanou).", @@ -505,6 +506,7 @@ "welcome2Text": "Také můžete získávat kupóny z mnoha aktivit.\nMohou být použity pro odemykání nových postav, map,\nmini-her, pro vstup do turnajů, a dalších.", "yourPowerRankingText": "Vaše pozice:" }, + "copyConfirmText": "Zkopírováno do schránky.", "copyOfText": "${NAME} Kopie", "copyText": "Kopírovat", "createEditPlayerText": "", @@ -634,7 +636,7 @@ "epicDescriptionFilterText": "${DESCRIPTION} V epickém slow motionu", "epicNameFilterText": "Epické ${NAME}", "errorAccessDeniedText": "přístup zamítnut", - "errorDeviceTimeIncorrectText": "Čas vašeho zařízení je mimo o ${HOURS} h.\nTo pravděpodobně způsobí problémy.\nProsím zkontrolujte nastavení času a časového pásma.", + "errorDeviceTimeIncorrectText": "Čas vašeho zařízení je špatně o ${HOURS} h.\nTo pravděpodobně způsobí problémy.\nProsím zkontrolujte nastavení času a časového pásma.", "errorOutOfDiskSpaceText": "není místo na disku", "errorSecureConnectionFailText": "Unable to establish secure cloud connection; network functionality may fail.", "errorText": "Chyba", @@ -1011,6 +1013,7 @@ "creditsText": "Tvůrci", "demoMenuText": "Demo menu", "endGameText": "Konec Hry", + "endTestText": "Konec Testu", "exitGameText": "Ukončit Hru", "exitToMenuText": "Vrátit se do menu?", "howToPlayText": "Jak hrát", diff --git a/dist/ba_data/data/languages/danish.json b/dist/ba_data/data/languages/danish.json index 0e70c12..0a31603 100644 --- a/dist/ba_data/data/languages/danish.json +++ b/dist/ba_data/data/languages/danish.json @@ -1174,7 +1174,7 @@ "winterSpecialText": "Vinterspecials", "youOwnThisText": "- Du ejer dette -" }, - "storeDescriptionText": "Vanvittigt spil med op plads til op til 8 spillere!\n\nSpræng dine venner eller computeren i luften i en turnering af eksplosive mini-spil som f.eks. find fanen, bombe-hockey og kamp til døden i slowmotion.\n\nNem styring og omfattende controllersupport gør det nemt at få op til 8 spillere med i kampen! Du kan endda bruge din mobil som controller via den gratis 'BombSquad Remote'-app.\n\nDer skydes!\n\nTjek www.froemling.net/bomsquad ud for mere information.", + "storeDescriptionText": "Vanvittigt spil med op plads til op til 8 spillere!\n\nSpræng dine venner eller computeren i luften i en turnering af eksplosive mini-spil som f.eks. find fanen, bombe-hockey og kamp til døden i slowmotion.\n\nNem styring og omfattende controllersupport gør det nemt at få op til 8 spillere med i kampen! Du kan endda bruge din mobil som controller via den gratis 'BombSquad Remote'-app.\n\n\n\nTjek bombsquadgame.com ud for mere information.", "storeDescriptions": { "blowUpYourFriendsText": "Spræng dine venner i luften", "competeInMiniGamesText": "Konkurrer i mini-spil lige fra racing til at flyve.", diff --git a/dist/ba_data/data/languages/english.json b/dist/ba_data/data/languages/english.json index e35d9ba..6e39dc6 100644 --- a/dist/ba_data/data/languages/english.json +++ b/dist/ba_data/data/languages/english.json @@ -13,6 +13,7 @@ "linkAccountsInstructionsNewText": "To link two accounts, generate a code on the first\nand enter that code on the second. Data from the\nsecond account will then be shared between both.\n(Data from the first account will be lost)\n\nYou can link up to ${COUNT} accounts.\n\nIMPORTANT: only link accounts that you own;\nIf you link with friends’ accounts you will not\nbe able to play online at the same time.", "linkAccountsText": "Link Accounts", "linkedAccountsText": "Linked Accounts:", + "manageAccountText": "Manage Account", "nameChangeConfirm": "Change your account name to ${NAME}?", "resetProgressConfirmNoAchievementsText": "This will reset your co-op progress and\nlocal high-scores (but not your tickets).\nThis cannot be undone. Are you sure?", "resetProgressConfirmText": "This will reset your co-op progress,\nachievements, and local high-scores\n(but not your tickets). This cannot\nbe undone. Are you sure?", @@ -1006,6 +1007,7 @@ "creditsText": "Credits", "demoMenuText": "Demo Menu", "endGameText": "End Game", + "endTestText": "End Test", "exitGameText": "Exit Game", "exitToMenuText": "Exit to menu?", "howToPlayText": "How to Play", diff --git a/dist/ba_data/data/languages/filipino.json b/dist/ba_data/data/languages/filipino.json index e98e35f..059d92d 100644 --- a/dist/ba_data/data/languages/filipino.json +++ b/dist/ba_data/data/languages/filipino.json @@ -14,6 +14,7 @@ "linkAccountsInstructionsText": "Para mag-ugnay ng dalawang account, gumawa ng code\nsa isa at ilagay ang code na iyon sa kabila.\nAng iyong pag-usad at imbentaryo ay pagsasamahin.\nMaaari mong i-ugnay hanggang sa ${COUNT} accounts.\n\nBabala lang; hindi na ito maibabalik!", "linkAccountsText": "Iugnay ang mga Account", "linkedAccountsText": "Naka-ugnay na mga Account", + "manageAccountText": "Patnubayan ang Account", "nameChangeConfirm": "Baguhin ang pangalan ng iyong account sa ${NAME}?", "resetProgressConfirmNoAchievementsText": "Ibabalik nito sa dati ang iyong pag-usad,\nat lokal na mga high-score (pwera sa ticket).\nHindi na ito maibabalik pa. Ipagpatuloy pa rin?", "resetProgressConfirmText": "Ibabalik nito sa dati ang iyong pag-usad,\nmga nakamtan, at lokal na mga high-score\n(pwera sa ticket). Hindi na ito maibabalik\npa. Ipagpatuloy pa rin?", @@ -1001,6 +1002,7 @@ "creditsText": "Mga Kredito", "demoMenuText": "Demo na Menu", "endGameText": "Itigil ang Laro", + "endTestText": "Itigil ang Test", "exitGameText": "Umalis sa Laro", "exitToMenuText": "Balik sa menu?", "howToPlayText": "Paano Maglaro", diff --git a/dist/ba_data/data/languages/french.json b/dist/ba_data/data/languages/french.json index 404efcf..1e7b6e9 100644 --- a/dist/ba_data/data/languages/french.json +++ b/dist/ba_data/data/languages/french.json @@ -15,6 +15,7 @@ "linkAccountsInstructionsText": "Pour lier deux comptes, générez un code sur l'un\nd'entre eux et entrer ce code sur l'autre.\nLes progrès et l'inventaire seront combinés.\nVous pouvez lier jusqu'à ${COUNT} comptes.\n\nIMPORTANT: reliez uniquement les comptes que vous possédez!\nSi vous reliez des comptes à vos amis, vous\nne pourrais pas jouer en même temps!\n\nAussi: cela ne peut actuellement pas être annulé, alors faites attention!", "linkAccountsText": "Lier vos comptes", "linkedAccountsText": "Comptes Liés:", + "manageAccountText": "Gérer compte", "nameChangeConfirm": "Changer le nom de votre compte pour ${NAME}?", "notLoggedInText": "", "resetProgressConfirmNoAchievementsText": "Ceci réinitialisera votre progression co-op et\nvos meilleurs scores locaux (mais pas vos tickets).\nCette action est irréversible. Êtes-vous certain(e)?", @@ -1056,6 +1057,7 @@ "creditsText": "Crédits", "demoMenuText": "Menu Démo", "endGameText": "Terminer le jeu", + "endTestText": "Terminer test", "exitGameText": "Quitter le jeu", "exitToMenuText": "Retourner au menu?", "howToPlayText": "Guide Pratique", @@ -1328,7 +1330,7 @@ "translationEditorButtonText": "Éditeur des Traductions de ${APP_NAME}", "translationFetchErrorText": "statut de la traduction indisponible", "translationFetchingStatusText": "vérification du statut de la traduction...", - "translationInformMe": "Informez-moi quand ma langue a besoin de mises à jour", + "translationInformMe": "M'avertir quand ma langue a besoin de mises à jour", "translationNoUpdateNeededText": "ce langage est à jour; woohoo!", "translationUpdateNeededText": "** ce langage à besoin des modifications!! **", "vrTestingText": "Test de la RV" diff --git a/dist/ba_data/data/languages/gibberish.json b/dist/ba_data/data/languages/gibberish.json index 7033d75..680e05a 100644 --- a/dist/ba_data/data/languages/gibberish.json +++ b/dist/ba_data/data/languages/gibberish.json @@ -17,6 +17,7 @@ "linkAccountsInstructionsText": "Tz lknf twz aoc coj, goi woc woef \nacoweof oac oia oow towjoifowj\nProwfo and in oir wojoc owinofiff.\nYouc owo iwoe oijf ${COUNT} accoaoijf.\n\nIMPCO oOIFOWEFJ\nowijecojwef\noijoicjwe\n\nAOicjowijeojwoeifjwef", "linkAccountsText": "Lnk Accnffzz", "linkedAccountsText": "Lnkfdf Accnrts:", + "manageAccountText": "JCowejerwoerjfwff", "nameChangeConfirm": "Cho weft cowejf coiwjocwe. ${NAME}?", "notLoggedInText": "", "resetProgressConfirmNoAchievementsText": "Thzz wflzz resta yrsr co-opz proglfzlz\nand hghzl scrdz. Itz cntljdf be zunfzz.\nArz yoz srz?", @@ -1081,6 +1082,7 @@ "creditsText": "Crizetzzts", "demoMenuText": "Dmzm Mnzz", "endGameText": "Enzs Gzmé´fez", + "endTestText": "Erj coiwjef", "exitGameText": "Excczzt Gamzzéé", "exitToMenuText": "Exít tzú ménu?", "howToPlayText": "Hwopm T´wPlayynf", diff --git a/dist/ba_data/data/languages/greek.json b/dist/ba_data/data/languages/greek.json index 5214a33..d999b6b 100644 --- a/dist/ba_data/data/languages/greek.json +++ b/dist/ba_data/data/languages/greek.json @@ -14,6 +14,7 @@ "linkAccountsInstructionsText": "Για να ενώσετε δυο λογαριασμούς, δημιουργήστε κωδικό\nσε έναν από αυτούς και εισάγετέ τον στον άλλο.\nΗ πρόοδος και τα περιεχόμενα θα συγχωνεύθουν.\nΜπορείτε να ενώσεις μέχρι και ${COUNT} λογαριασμούς.\n\nΠροσοχή: η ενέργεια είναι μη αναστρέψιμη!\n\n", "linkAccountsText": "Δεσμεύστε Λογαριασμούς", "linkedAccountsText": "Δεσεμευμένοι λογαριασμοί:", + "manageAccountText": "Διαχείρειση λογαριασμού", "nameChangeConfirm": "Αλλαγή του ονόματος του λογαριασμού σας σε ${NAME};", "resetProgressConfirmNoAchievementsText": "Αυτό θα επαναφέρει τη πρόοδο σας στη \"συνεργασία\" και τις\nτοπικές υψηλές σας βαθμολογίες (αλλά όχι τα εισιτήρια).\nΗ ενέργεια είναι μη αντιστρέψιμη. Είστε σίγουροι;", "resetProgressConfirmText": "Αυτό θα επαναφέρει τη πρόοδο σας στη \"συνεργασία\",\nτα επιτεύγματα και τις τοπικές υψηλές σας\nβαθμολογίες (αλλά όχι τα εισιτήρια). Η ενέργεια\nείναι μη αντιστρέψιμη. Είστε σίγουροι;", @@ -1002,6 +1003,7 @@ "creditsText": "Εύσημα", "demoMenuText": "Μενού Επίδειξης", "endGameText": "Τέλος Παιχνιδιού", + "endTestText": "Τέλος τεστ", "exitGameText": "Έξοδος Παιχνιδιού", "exitToMenuText": "Έξοδος στο μενού;", "howToPlayText": "Πως Παίζεται;", diff --git a/dist/ba_data/data/languages/indonesian.json b/dist/ba_data/data/languages/indonesian.json index 7426123..7617211 100644 --- a/dist/ba_data/data/languages/indonesian.json +++ b/dist/ba_data/data/languages/indonesian.json @@ -15,6 +15,7 @@ "linkAccountsInstructionsText": "Untuk menghubungkan dua akun, buat kode di salah satu\ndari mereka dan masukkan kode itu di sisi lain.\nKemajuan dan persediaan akan digabungkan.\nAnda dapat menghubungkan hingga ${COUNT} akun.\n\nPENTING: Hanya hubungkan akun yang anda miliki!\nJika Anda menghubungkan akun dengan teman anda, anda\ntidak akan bisa bermain bersamaan!\n\nJuga: ini tidak dapat dibatalkan, jadi berhati-hatilah!", "linkAccountsText": "Hubungkan Akun", "linkedAccountsText": "Akun yang Terhubung:", + "manageAccountText": "Pengaturan Akun", "nameChangeConfirm": "Ganti namamu menjadi ${NAME}?", "resetProgressConfirmNoAchievementsText": "Tindakan ini akan mengatur ulang kemajuan co-op dan\nskor tertinggi Kamu (kecuali tiket).\nTidak dapat dibatalkan. Apakah Kamu yakin?", "resetProgressConfirmText": "Ini akan mengatur ulang kemajuan co-op,\npencapaian, dan skor tertinggi Kamu\n(kecuali tiket). Ini tidak dapat\ndibatalkan. Kamu yakin?", @@ -1000,6 +1001,7 @@ "creditsText": "Kredit", "demoMenuText": "Menu Demo", "endGameText": "Akhiri Permainan", + "endTestText": "Akhiri Pengetesan", "exitGameText": "Keluar dari Permainan", "exitToMenuText": "Ke Menu?", "howToPlayText": "Cara bermain", diff --git a/dist/ba_data/data/languages/italian.json b/dist/ba_data/data/languages/italian.json index 9a4dda2..d0bce35 100644 --- a/dist/ba_data/data/languages/italian.json +++ b/dist/ba_data/data/languages/italian.json @@ -16,6 +16,7 @@ "linkAccountsInstructionsText": "Per collegare due account, crea un codice su uno\ndei dispositivi e inserisci quel codice negli altri. \nProgressi e inventario verranno combinati.\nPuoi collegare fino a ${COUNT} account.\n\nIMPORTANTE: Collega solo account che possiedi!\nSe colleghi un account con un tuo amico non potrete\ngiocare allo stesso momento!\n \nInoltre: questa operazione non può essere annullata, quindi stai attento!", "linkAccountsText": "Collega account", "linkedAccountsText": "Account Collegati:", + "manageAccountText": "Gestisci account", "nameChangeConfirm": "Confermi di voler modificare il tuo nome in ${NAME}?", "notLoggedInText": "", "resetProgressConfirmNoAchievementsText": "Stai per cancellare i tuoi progressi in\nmodalità cooperativa, i tuoi obbiettivi, i tuoi punteggi\nlocali (ma non i tuoi biglietti). \n\nL'operazione è irreversibile: continuare?", @@ -1045,6 +1046,7 @@ "creditsText": "Riconoscimenti", "demoMenuText": "Menu Demo", "endGameText": "Chiudi partita", + "endTestText": "Termina Test", "exitGameText": "Esci dal gioco", "exitToMenuText": "Tornare al menu?", "howToPlayText": "Come Giocare", diff --git a/dist/ba_data/data/languages/portuguese.json b/dist/ba_data/data/languages/portuguese.json index 991ef53..da0d4c8 100644 --- a/dist/ba_data/data/languages/portuguese.json +++ b/dist/ba_data/data/languages/portuguese.json @@ -15,6 +15,7 @@ "linkAccountsInstructionsText": "Para vincular duas contas, gere um código\nem uma delas e o insira na outra.\nProgresso e inventário serão combinados.\nVocê pode vincular até ${COUNT} contas.\n\nIMPORTANTE: Vincule apenas as contas que você possui!\nSe você vincular contas a seus amigos, você\nnão será capaz de jogar ao mesmo tempo!\n\nAlém disso: isso não pode ser desfeito atualmente, então tenha cuidado!", "linkAccountsText": "Vincular contas", "linkedAccountsText": "Contas vinculadas:", + "manageAccountText": "Gerenciar Conta", "nameChangeConfirm": "Mudar o nome da sua conta para ${NAME}?", "notLoggedInText": "", "resetProgressConfirmNoAchievementsText": "Isto reiniciará o seu progresso no cooperativo e\nas suas pontuações (mas não os seus bilhetes).\nIsto não pode ser desfeito. Tem certeza?", @@ -1066,6 +1067,7 @@ "creditsText": "Créditos", "demoMenuText": "Menu de demonstração", "endGameText": "Finalizar jogo", + "endTestText": "Finalizar Teste", "exitGameText": "Sair do jogo", "exitToMenuText": "Sair para o menu?", "howToPlayText": "Como jogar", diff --git a/dist/ba_data/data/languages/russian.json b/dist/ba_data/data/languages/russian.json index 00c7bb4..47c403b 100644 --- a/dist/ba_data/data/languages/russian.json +++ b/dist/ba_data/data/languages/russian.json @@ -16,6 +16,7 @@ "linkAccountsInstructionsText": "Для связки двух аккаунтов, создайте код на одном\nиз них и введите код на другом.\nПрогресс и инвентарь будут объединены.\nВы можете связать до ${COUNT} аккаунтов.", "linkAccountsText": "Связать акаунты", "linkedAccountsText": "Привязанные аккаунты:", + "manageAccountText": "Управление аккаунтом", "nameChangeConfirm": "Вы уверены, что хотите сменить имя аккаунта на ${NAME}?", "notLoggedInText": "<не авторизован>", "resetProgressConfirmNoAchievementsText": "Это сбросит весь ваш кооперативный прогресс\nи локальные рекорды (но не билеты).\nЭтот процесс необратим. Вы уверены?", @@ -1049,6 +1050,7 @@ "creditsText": "Благодарности", "demoMenuText": "Меню демо", "endGameText": "Закончить игру", + "endTestText": "Завершить тест", "exitGameText": "Выйти из игры", "exitToMenuText": "Выйти в меню?", "howToPlayText": "Как играть", diff --git a/dist/ba_data/data/languages/spanish.json b/dist/ba_data/data/languages/spanish.json index 0e44d8a..286c945 100644 --- a/dist/ba_data/data/languages/spanish.json +++ b/dist/ba_data/data/languages/spanish.json @@ -16,6 +16,7 @@ "linkAccountsInstructionsText": "Para enlazar dos cuentas, genera un código en una\n de ellas y escribe el código en la otra.\nEl progreso e accesorios se combinarán.\nPuedes enlazar hasta ${COUNT} cuentas. \n\nIMPORTANTE: Solo enlaza cuentas tuyas!\n\nSi enlazas cuentas con tus amigos no podrán jugar al mismo tiempo!\n\nTambién: esto no se puede deshacer actualmente, así que se cuidadoso!", "linkAccountsText": "Enlazar Cuentas", "linkedAccountsText": "Cuentas enlazadas:", + "manageAccountText": "administrar cuenta", "nameChangeConfirm": "¿Cambiar tu nombre a ${NAME}?", "notLoggedInText": "", "resetProgressConfirmNoAchievementsText": "Esto reiniciará tu progreso en el modo\ncooperativo y tus puntajes (A excepción de tus tickets).\nNo podrás recuperar los cambios. ¿Estás seguro?", @@ -1062,6 +1063,7 @@ "creditsText": "Créditos", "demoMenuText": "Menú Demo", "endGameText": "Salir del juego", + "endTestText": "Prueba final", "exitGameText": "Salir del juego", "exitToMenuText": "¿Salir al menú?", "howToPlayText": "Cómo jugar", diff --git a/dist/ba_data/data/languages/tamil.json b/dist/ba_data/data/languages/tamil.json index a12c8a5..72abd1a 100644 --- a/dist/ba_data/data/languages/tamil.json +++ b/dist/ba_data/data/languages/tamil.json @@ -13,6 +13,7 @@ "linkAccountsInstructionsNewText": "இரண்டு கணக்குகளை இணைக்க, முதலில் ஒரு குறியீட்டை உருவாக்கவும்\nஇரண்டாவது குறியீட்டை உள்ளிடவும். \n இலிருந்து தரவு\nஇரண்டாவது கணக்கு பின்னர் இருவருக்கும் இடையே பகிரப்படும். \n(முதல் கணக்கிலிருந்து தரவு இழக்கப்படும்)\n\nநீங்கள் ${COUNT} கணக்குகள் வரை இணைக்க முடியும்.\n\nமுக்கியம்: உங்களுக்கு சொந்தமான கணக்குகளை மட்டுமே இணைக்கவும்;\nநண்பர்களின் கணக்குகளுடன் நீங்கள் இணைத்தால் நீங்கள் ஒரே நேரத்தில் ஆன்லைனில் விளையாட முடியாது.", "linkAccountsText": "கணக்குகளை இணைக்கவும்", "linkedAccountsText": "இணைக்கப்பட்ட கணக்குகள்:", + "manageAccountText": "கணக்கை நிர்வகி", "nameChangeConfirm": "உங்கள் கணக்கின் பெயரை ${NAME}க்கு மாற்ற", "resetProgressConfirmNoAchievementsText": "இது உங்கள் கூட்டு முன்னேற்றங்கள் மற்றும் கருவி உயர்மதிப்பெண்கள் ஆகியவற்றை அழிக்கும் (ஆனால் உங்கள் சீட்டுகள் அல்ல).\nஇச்செயலை பின்வாங்க முடியாது.\nஉறுதியா?", "resetProgressConfirmText": "இது உங்கள் கூட்டு முன்னேற்றங்கள், சாதனைகள், மற்றும் கருவி உயர்மதிப்பெண்கள் ஆகியவற்றை அழிக்கும் \n(ஆனால் உங்கள் சீட்டுகள் அல்ல).\nஇச்செயலை பின்வாங்க முடியாது.\nஉறுதியா?", @@ -999,6 +1000,7 @@ "creditsText": "வரவுகள்", "demoMenuText": "டெமோ மெனு", "endGameText": "விளையாட்டை வெளியேறு", + "endTestText": "முடிவு சோதனை", "exitGameText": "விளையாட்டை வெளியேறு", "exitToMenuText": "மெனுவிலிருந்து வெளியேறவா?", "howToPlayText": "எப்படி விளையாடுவது", diff --git a/dist/ba_data/data/languages/turkish.json b/dist/ba_data/data/languages/turkish.json index b91ab74..635ec6f 100644 --- a/dist/ba_data/data/languages/turkish.json +++ b/dist/ba_data/data/languages/turkish.json @@ -15,6 +15,7 @@ "linkAccountsInstructionsText": "İki hesabı birbirine bağlamak için, birinde kod oluşturun\nve o kodu diğerinde girin.\nİlerlemeler ve envanterler birleşecektir.\n${COUNT} taneye kadar hesap bağlayabilirsiniz.\n\nÖNEMLİ: Yalnızca kendi hesaplarını bağla!\nEğer arkadaşınla hesapları bağlarsan\naynı anda oynayamayacaksınız!\n\nAyrıca: bu işlem şu anda geri alınamaz, yani dikkatli ol!", "linkAccountsText": "Hesapları Bağla", "linkedAccountsText": "Bağlı Hesaplar:", + "manageAccountText": "Hesabı Yönet", "nameChangeConfirm": "Hesap adın ${NAME} olarak değiştirilsinmi ?", "resetProgressConfirmNoAchievementsText": "Bu co-op ilerlemeyi ve \nlokaldeki yüksek puanlarnı sıfırlar(ama biletlerini değil).\nBu geri alınamaz. Emin misiniz?", "resetProgressConfirmText": "Bu co-op ilerlemeyi, başarıları ve \nEn yüksek skorları sıfırlayacak \n(ama biletlerini değil). Bu işlem\ngeri alınamaz. Emin misin?", @@ -999,6 +1000,7 @@ "creditsText": "Jenerik", "demoMenuText": "Demo Menü", "endGameText": "Oyunu Bitir", + "endTestText": "Testi Bitir", "exitGameText": "Oyundan Çık", "exitToMenuText": "Menüye Git?", "howToPlayText": "Nasıl Oynanır", diff --git a/dist/ba_data/data/languages/ukrainian.json b/dist/ba_data/data/languages/ukrainian.json index 5f09baa..dff000f 100644 --- a/dist/ba_data/data/languages/ukrainian.json +++ b/dist/ba_data/data/languages/ukrainian.json @@ -15,6 +15,7 @@ "linkAccountsInstructionsText": "Щоб зв'язати дви обликови записи, створювати код на одному з них и ввести цей код на инший. Прогрес та инвентаризации будуць об'эднанни. Ви можете зв'язати ${COUNT} рахунки.", "linkAccountsText": "Зв'язати акаунти", "linkedAccountsText": "Зв'язані акаунти:", + "manageAccountText": "Контролювати аккаунтом", "nameChangeConfirm": "Ви впевнені що хочете змінити ім'я на ${NAME}?", "resetProgressConfirmNoAchievementsText": "Це скине весь ваш кооперативний прогрес\nі локальні кращі результати (крім квитків).\nЦей процес є незворотнім. Ви впевнені?", "resetProgressConfirmText": "Це скине весь ваш кооперативний\nпрогрес, досягнення і локальні результати\n(Крім квитків). Цей процес є незворотнім.\nВи впевнені?", @@ -499,6 +500,7 @@ "welcome2Text": "Ви також можете заробити квитки від багатьох однакових видів діяльності.\nКвитки можуть бути використані, щоб розблокувати нових персонажів, карти та\nміні-ігри, щоб увійти турніри, і багато іншого.", "yourPowerRankingText": "Ваш ранг:" }, + "copyConfirmText": "Скопійовано до буферу обміну", "copyOfText": "Копія ${NAME}", "copyText": "Копія", "createEditPlayerText": "<Створення / редагування гравця>", @@ -999,6 +1001,7 @@ "creditsText": "Подяки", "demoMenuText": "Меню прикладів", "endGameText": "Завершити гру", + "endTestText": "Закінчити тест", "exitGameText": "Вийти з гри", "exitToMenuText": "Вийти в меню?", "howToPlayText": "Як грати", diff --git a/dist/ba_data/data/languages/venetian.json b/dist/ba_data/data/languages/venetian.json index 7813a92..4021d32 100644 --- a/dist/ba_data/data/languages/venetian.json +++ b/dist/ba_data/data/languages/venetian.json @@ -13,6 +13,7 @@ "linkAccountsInstructionsNewText": "Par cołegar do account, jènara un còdaze inte'l primo\ndispozidivo e insarìseło inte’l segondo. I dati de’l\nsegondo account i vegnarà sparpagnài so tuti do i account\n(i dati de’l primo account i ndarà perdesti).\n\nTe połi cołegar fin a ${COUNT} account.\n\nINPORTANTE: cołega soło i account de to propiedà.\nSe te cołeghi account de calche to amigo, no sarè\npì boni de zugar online inte’l mèdemo momento.", "linkAccountsText": "Cołega account", "linkedAccountsText": "Account cołegài:", + "manageAccountText": "Jestisi account", "nameChangeConfirm": "Vutu canbiar el nome de’l to account co ${NAME}?", "resetProgressConfirmNoAchievementsText": "Te si drio ełimenar i to progresi so ła modałidà\ncooparadiva e i to punteji łogałi (ma miga i to biłieti).\n’Sta asion no ła połe pì èsar anułada. Vutu ndar vanti?", "resetProgressConfirmText": "Te si drio ełimenar i to progresi so ła\nmodałidà cooparadiva, i to obietivi e i to punteji\nłogałi (ma miga i to biłieti). ’Sta asion\nno ła połe pì èsar anułada. Vutu ndar vanti?", @@ -487,7 +488,7 @@ "proMultInfoText": "I zugadori co'l mejoramento ${PRO}\ni beca el ${PERCENT}% de punti in pì.", "seeMoreText": "Clasìfega conpleta", "skipWaitText": "Vanti suito", - "timeRemainingText": "Tenpo remanente", + "timeRemainingText": "Tenpo che resta", "toRankedText": "par ndar sù de pozision", "totalText": "totałe", "tournamentInfoText": "Conpeti co cheł'altri zugadori par\nndar sù de puntejo inte ła to łega.\n\nCo'l tornèo el fenirà, i zugadori pì\nbrai i vegnarà reconpensài co i premi.", @@ -996,6 +997,7 @@ "creditsText": "Mension", "demoMenuText": "Menù demo", "endGameText": "Sara sù partìa", + "endTestText": "Fenisi prova", "exitGameText": "Sortisi da'l zugo", "exitToMenuText": "Vutu tornar inte'l menù?", "howToPlayText": "Come zugar", @@ -1238,7 +1240,7 @@ "enablePackageModsText": "Ativa pacheti mod łogałi", "enterPromoCodeText": "Insarisi còdaze", "forTestingText": "Nota: vałori vàłidi soło par proe. Sortendo da l'apl łi vegnarà perdesti.", - "helpTranslateText": "Łe tradusion de ${APP_NAME} łe ze curàe da vołontari.\nSe A te vol darghe na ociada a cheła veneta, struca so'l boton\ncuà soto. Curada da Còdaze Veneto: codazeveneto@gmail.com", + "helpTranslateText": "Łe tradusion de ${APP_NAME} łe ze curàe da vołontari.\nSe te vołi darghe na ociada a cheła veneta, struca so'l boton\ncuà soto. Curada da VeC: venetianlanguage@gmail.com", "kickIdlePlayersText": "Para fora zugadori in sonera", "kidFriendlyModeText": "Modałidà bocia (viołensa reduzesta, evc)", "languageText": "Łengua", diff --git a/dist/ba_data/python/_bainternal.py b/dist/ba_data/python/_bainternal.py index cf89a39..d1bf810 100644 --- a/dist/ba_data/python/_bainternal.py +++ b/dist/ba_data/python/_bainternal.py @@ -35,6 +35,7 @@ from typing import TYPE_CHECKING, TypeVar if TYPE_CHECKING: from typing import Any, Callable + _T = TypeVar('_T') @@ -44,13 +45,16 @@ def _uninferrable() -> Any: return _not_a_real_variable # type: ignore -def add_transaction(transaction: dict, - callback: Callable | None = None) -> None: +def add_transaction( + transaction: dict, callback: Callable | None = None +) -> None: + """(internal)""" return None def game_service_has_leaderboard(game: str, config: str) -> bool: + """(internal) Given a game and config string, returns whether there is a leaderboard @@ -60,6 +64,7 @@ def game_service_has_leaderboard(game: str, config: str) -> bool: def get_master_server_address(source: int = -1, version: int = 1) -> str: + """(internal) Return the address of the master server. @@ -68,66 +73,79 @@ def get_master_server_address(source: int = -1, version: int = 1) -> str: def get_news_show() -> str: + """(internal)""" return str() def get_price(item: str) -> str | None: + """(internal)""" return '' def get_public_login_id() -> str | None: + """(internal)""" return '' def get_purchased(item: str) -> bool: + """(internal)""" return bool() def get_purchases_state() -> int: + """(internal)""" return int() def get_v1_account_display_string(full: bool = True) -> str: + """(internal)""" return str() def get_v1_account_misc_read_val(name: str, default_value: Any) -> Any: + """(internal)""" return _uninferrable() def get_v1_account_misc_read_val_2(name: str, default_value: Any) -> Any: + """(internal)""" return _uninferrable() def get_v1_account_misc_val(name: str, default_value: Any) -> Any: + """(internal)""" return _uninferrable() def get_v1_account_name() -> str: + """(internal)""" return str() def get_v1_account_state() -> str: + """(internal)""" return str() def get_v1_account_state_num() -> int: + """(internal)""" return int() def get_v1_account_ticket_count() -> int: + """(internal) Returns the number of tickets for the current account. @@ -136,31 +154,37 @@ def get_v1_account_ticket_count() -> int: def get_v1_account_type() -> str: + """(internal)""" return str() def get_v2_fleet() -> str: + """(internal)""" return str() def have_outstanding_transactions() -> bool: + """(internal)""" return bool() def in_game_purchase(item: str, price: int) -> None: + """(internal)""" return None def is_blessed() -> bool: + """(internal)""" return bool() def mark_config_dirty() -> None: + """(internal) Category: General Utility Functions @@ -169,36 +193,43 @@ def mark_config_dirty() -> None: def power_ranking_query(callback: Callable, season: Any = None) -> None: + """(internal)""" return None def purchase(item: str) -> None: + """(internal)""" return None def report_achievement(achievement: str, pass_to_account: bool = True) -> None: + """(internal)""" return None def reset_achievements() -> None: + """(internal)""" return None def restore_purchases() -> None: + """(internal)""" return None def run_transactions() -> None: + """(internal)""" return None def sign_in_v1(account_type: str) -> None: + """(internal) Category: General Utility Functions @@ -207,6 +238,7 @@ def sign_in_v1(account_type: str) -> None: def sign_out_v1(v2_embedded: bool = False) -> None: + """(internal) Category: General Utility Functions @@ -214,17 +246,20 @@ def sign_out_v1(v2_embedded: bool = False) -> None: return None -def submit_score(game: str, - config: str, - name: Any, - score: int | None, - callback: Callable, - friend_callback: Callable | None, - order: str = 'increasing', - tournament_id: str | None = None, - score_type: str = 'points', - campaign: str | None = None, - level: str | None = None) -> None: +def submit_score( + game: str, + config: str, + name: Any, + score: int | None, + callback: Callable, + friend_callback: Callable | None, + order: str = 'increasing', + tournament_id: str | None = None, + score_type: str = 'points', + campaign: str | None = None, + level: str | None = None, +) -> None: + """(internal) Submit a score to the server; callback will be called with the results. @@ -235,7 +270,9 @@ def submit_score(game: str, return None -def tournament_query(callback: Callable[[dict | None], None], - args: dict) -> None: +def tournament_query( + callback: Callable[[dict | None], None], args: dict +) -> None: + """(internal)""" return None diff --git a/dist/ba_data/python/ba/__init__.py b/dist/ba_data/python/ba/__init__.py index 43d9897..290d848 100644 --- a/dist/ba_data/python/ba/__init__.py +++ b/dist/ba_data/python/ba/__init__.py @@ -9,15 +9,62 @@ In some specific cases you may need to pull in individual submodules instead. # pylint: disable=redefined-builtin from _ba import ( - CollideModel, Context, ContextCall, Data, InputDevice, Material, Model, - Node, SessionPlayer, Sound, Texture, Timer, Vec3, Widget, buttonwidget, - camerashake, checkboxwidget, columnwidget, containerwidget, do_once, - emitfx, getactivity, getcollidemodel, getmodel, getnodes, getsession, - getsound, gettexture, hscrollwidget, imagewidget, newactivity, newnode, - playsound, printnodes, printobjects, pushcall, quit, rowwidget, safecolor, - screenmessage, scrollwidget, set_analytics_screen, charstr, textwidget, - time, timer, open_url, widget, clipboard_is_supported, clipboard_has_text, - clipboard_get_text, clipboard_set_text, getdata, in_logic_thread) + CollideModel, + Context, + ContextCall, + Data, + InputDevice, + Material, + Model, + Node, + SessionPlayer, + Sound, + Texture, + Timer, + Vec3, + Widget, + buttonwidget, + camerashake, + checkboxwidget, + columnwidget, + containerwidget, + do_once, + emitfx, + getactivity, + getcollidemodel, + getmodel, + getnodes, + getsession, + getsound, + gettexture, + hscrollwidget, + imagewidget, + newactivity, + newnode, + playsound, + printnodes, + printobjects, + pushcall, + quit, + rowwidget, + safecolor, + screenmessage, + scrollwidget, + set_analytics_screen, + charstr, + textwidget, + time, + timer, + open_url, + widget, + clipboard_is_supported, + clipboard_has_text, + clipboard_get_text, + clipboard_set_text, + getdata, + in_logic_thread, +) +from ba._accountv2 import AccountV2Handle from ba._activity import Activity from ba._plugin import PotentialPlugin, Plugin, PluginSubsystem from ba._actor import Actor @@ -27,21 +74,50 @@ from ba._app import App from ba._cloud import CloudSubsystem from ba._coopgame import CoopGameActivity from ba._coopsession import CoopSession -from ba._dependency import (Dependency, DependencyComponent, DependencySet, - AssetPackage) -from ba._generated.enums import (TimeType, Permission, TimeFormat, SpecialChar, - InputType, UIScale) +from ba._dependency import ( + Dependency, + DependencyComponent, + DependencySet, + AssetPackage, +) +from ba._generated.enums import ( + TimeType, + Permission, + TimeFormat, + SpecialChar, + InputType, + UIScale, +) from ba._error import ( - print_exception, print_error, ContextError, NotFoundError, - PlayerNotFoundError, SessionPlayerNotFoundError, NodeNotFoundError, - ActorNotFoundError, InputDeviceNotFoundError, WidgetNotFoundError, - ActivityNotFoundError, TeamNotFoundError, SessionTeamNotFoundError, - SessionNotFoundError, DelegateNotFoundError, DependencyError) + print_exception, + print_error, + ContextError, + NotFoundError, + PlayerNotFoundError, + SessionPlayerNotFoundError, + NodeNotFoundError, + ActorNotFoundError, + InputDeviceNotFoundError, + WidgetNotFoundError, + ActivityNotFoundError, + TeamNotFoundError, + SessionTeamNotFoundError, + SessionNotFoundError, + DelegateNotFoundError, + DependencyError, +) from ba._freeforallsession import FreeForAllSession from ba._gameactivity import GameActivity from ba._gameresults import GameResults -from ba._settings import (Setting, IntSetting, FloatSetting, ChoiceSetting, - BoolSetting, IntChoiceSetting, FloatChoiceSetting) +from ba._settings import ( + Setting, + IntSetting, + FloatSetting, + ChoiceSetting, + BoolSetting, + IntChoiceSetting, + FloatChoiceSetting, +) from ba._language import Lstr, LanguageSubsystem from ba._map import Map, getmaps from ba._session import Session @@ -57,23 +133,53 @@ from ba._appconfig import AppConfig from ba._appdelegate import AppDelegate from ba._apputils import is_browser_likely_available, garbage_collect from ba._campaign import Campaign -from ba._gameutils import (GameTip, animate, animate_array, show_damage_count, - timestring, cameraflash) -from ba._general import (WeakCall, Call, existing, Existable, - verify_object_death, storagename, getclass) +from ba._gameutils import ( + GameTip, + animate, + animate_array, + show_damage_count, + timestring, + cameraflash, +) +from ba._general import ( + WeakCall, + Call, + existing, + Existable, + verify_object_death, + storagename, + getclass, +) from ba._keyboard import Keyboard from ba._level import Level from ba._lobby import Lobby, Chooser from ba._math import normalized_color, is_point_in_box, vec3validate from ba._meta import MetadataSubsystem -from ba._messages import (UNHANDLED, OutOfBoundsMessage, DeathType, DieMessage, - PlayerDiedMessage, StandMessage, PickUpMessage, - DropMessage, PickedUpMessage, DroppedMessage, - ShouldShatterMessage, ImpactDamageMessage, - FreezeMessage, ThawMessage, HitMessage, - CelebrateMessage) -from ba._music import (setmusic, MusicPlayer, MusicType, MusicPlayMode, - MusicSubsystem) +from ba._messages import ( + UNHANDLED, + OutOfBoundsMessage, + DeathType, + DieMessage, + PlayerDiedMessage, + StandMessage, + PickUpMessage, + DropMessage, + PickedUpMessage, + DroppedMessage, + ShouldShatterMessage, + ImpactDamageMessage, + FreezeMessage, + ThawMessage, + HitMessage, + CelebrateMessage, +) +from ba._music import ( + setmusic, + MusicPlayer, + MusicType, + MusicPlayMode, + MusicSubsystem, +) from ba._powerup import PowerupMessage, PowerupAcceptMessage from ba._multiteamsession import MultiTeamSession from ba.ui import Window, UIController, uicleanupcheck @@ -82,47 +188,186 @@ from ba._collision import Collision, getcollision app: App __all__ = [ - 'Achievement', 'AchievementSubsystem', 'Activity', 'ActivityNotFoundError', - 'Actor', 'ActorNotFoundError', 'animate', 'animate_array', 'app', 'App', - 'AppConfig', 'AppDelegate', 'AssetPackage', 'BoolSetting', 'buttonwidget', - 'Call', 'cameraflash', 'camerashake', 'Campaign', 'CelebrateMessage', - 'charstr', 'checkboxwidget', 'ChoiceSetting', 'Chooser', - 'clipboard_get_text', 'clipboard_has_text', 'clipboard_is_supported', - 'clipboard_set_text', 'CollideModel', 'Collision', 'columnwidget', - 'containerwidget', 'Context', 'ContextCall', 'ContextError', - 'CloudSubsystem', 'CoopGameActivity', 'CoopSession', 'Data', 'DeathType', - 'DelegateNotFoundError', 'Dependency', 'DependencyComponent', - 'DependencyError', 'DependencySet', 'DieMessage', 'do_once', 'DropMessage', - 'DroppedMessage', 'DualTeamSession', 'emitfx', 'EmptyPlayer', 'EmptyTeam', - 'Existable', 'existing', 'FloatChoiceSetting', 'FloatSetting', - 'FreeForAllSession', 'FreezeMessage', 'GameActivity', 'GameResults', - 'GameTip', 'garbage_collect', 'getactivity', 'getclass', 'getcollidemodel', - 'getcollision', 'getdata', 'getmaps', 'getmodel', 'getnodes', 'getsession', - 'getsound', 'gettexture', 'HitMessage', 'hscrollwidget', 'imagewidget', - 'ImpactDamageMessage', 'in_logic_thread', 'InputDevice', - 'InputDeviceNotFoundError', 'InputType', 'IntChoiceSetting', 'IntSetting', - 'is_browser_likely_available', 'is_point_in_box', 'Keyboard', - 'LanguageSubsystem', 'Level', 'Lobby', 'Lstr', 'Map', 'Material', - 'MetadataSubsystem', 'Model', 'MultiTeamSession', 'MusicPlayer', - 'MusicPlayMode', 'MusicSubsystem', 'MusicType', 'newactivity', 'newnode', - 'Node', 'NodeActor', 'NodeNotFoundError', 'normalized_color', - 'NotFoundError', 'open_url', 'OutOfBoundsMessage', 'Permission', - 'PickedUpMessage', 'PickUpMessage', 'Player', 'PlayerDiedMessage', - 'PlayerInfo', 'PlayerNotFoundError', 'PlayerRecord', 'PlayerScoredMessage', - 'playsound', 'Plugin', 'PluginSubsystem', 'PotentialPlugin', - 'PowerupAcceptMessage', 'PowerupMessage', 'print_error', 'print_exception', - 'printnodes', 'printobjects', 'pushcall', 'quit', 'rowwidget', 'safecolor', - 'ScoreConfig', 'ScoreType', 'screenmessage', 'scrollwidget', - 'ServerController', 'Session', 'SessionNotFoundError', 'SessionPlayer', - 'SessionPlayerNotFoundError', 'SessionTeam', 'SessionTeamNotFoundError', - 'set_analytics_screen', 'setmusic', 'Setting', 'ShouldShatterMessage', - 'show_damage_count', 'Sound', 'SpecialChar', 'StandLocation', - 'StandMessage', 'Stats', 'storagename', 'Team', 'TeamGameActivity', - 'TeamNotFoundError', 'Texture', 'textwidget', 'ThawMessage', 'time', - 'TimeFormat', 'Timer', 'timer', 'timestring', 'TimeType', 'uicleanupcheck', - 'UIController', 'UIScale', 'UISubsystem', 'UNHANDLED', 'Vec3', - 'vec3validate', 'verify_object_death', 'WeakCall', 'Widget', 'widget', - 'WidgetNotFoundError', 'Window' + 'AccountV2Handle', + 'Achievement', + 'AchievementSubsystem', + 'Activity', + 'ActivityNotFoundError', + 'Actor', + 'ActorNotFoundError', + 'animate', + 'animate_array', + 'app', + 'App', + 'AppConfig', + 'AppDelegate', + 'AssetPackage', + 'BoolSetting', + 'buttonwidget', + 'Call', + 'cameraflash', + 'camerashake', + 'Campaign', + 'CelebrateMessage', + 'charstr', + 'checkboxwidget', + 'ChoiceSetting', + 'Chooser', + 'clipboard_get_text', + 'clipboard_has_text', + 'clipboard_is_supported', + 'clipboard_set_text', + 'CollideModel', + 'Collision', + 'columnwidget', + 'containerwidget', + 'Context', + 'ContextCall', + 'ContextError', + 'CloudSubsystem', + 'CoopGameActivity', + 'CoopSession', + 'Data', + 'DeathType', + 'DelegateNotFoundError', + 'Dependency', + 'DependencyComponent', + 'DependencyError', + 'DependencySet', + 'DieMessage', + 'do_once', + 'DropMessage', + 'DroppedMessage', + 'DualTeamSession', + 'emitfx', + 'EmptyPlayer', + 'EmptyTeam', + 'Existable', + 'existing', + 'FloatChoiceSetting', + 'FloatSetting', + 'FreeForAllSession', + 'FreezeMessage', + 'GameActivity', + 'GameResults', + 'GameTip', + 'garbage_collect', + 'getactivity', + 'getclass', + 'getcollidemodel', + 'getcollision', + 'getdata', + 'getmaps', + 'getmodel', + 'getnodes', + 'getsession', + 'getsound', + 'gettexture', + 'HitMessage', + 'hscrollwidget', + 'imagewidget', + 'ImpactDamageMessage', + 'in_logic_thread', + 'InputDevice', + 'InputDeviceNotFoundError', + 'InputType', + 'IntChoiceSetting', + 'IntSetting', + 'is_browser_likely_available', + 'is_point_in_box', + 'Keyboard', + 'LanguageSubsystem', + 'Level', + 'Lobby', + 'Lstr', + 'Map', + 'Material', + 'MetadataSubsystem', + 'Model', + 'MultiTeamSession', + 'MusicPlayer', + 'MusicPlayMode', + 'MusicSubsystem', + 'MusicType', + 'newactivity', + 'newnode', + 'Node', + 'NodeActor', + 'NodeNotFoundError', + 'normalized_color', + 'NotFoundError', + 'open_url', + 'OutOfBoundsMessage', + 'Permission', + 'PickedUpMessage', + 'PickUpMessage', + 'Player', + 'PlayerDiedMessage', + 'PlayerInfo', + 'PlayerNotFoundError', + 'PlayerRecord', + 'PlayerScoredMessage', + 'playsound', + 'Plugin', + 'PluginSubsystem', + 'PotentialPlugin', + 'PowerupAcceptMessage', + 'PowerupMessage', + 'print_error', + 'print_exception', + 'printnodes', + 'printobjects', + 'pushcall', + 'quit', + 'rowwidget', + 'safecolor', + 'ScoreConfig', + 'ScoreType', + 'screenmessage', + 'scrollwidget', + 'ServerController', + 'Session', + 'SessionNotFoundError', + 'SessionPlayer', + 'SessionPlayerNotFoundError', + 'SessionTeam', + 'SessionTeamNotFoundError', + 'set_analytics_screen', + 'setmusic', + 'Setting', + 'ShouldShatterMessage', + 'show_damage_count', + 'Sound', + 'SpecialChar', + 'StandLocation', + 'StandMessage', + 'Stats', + 'storagename', + 'Team', + 'TeamGameActivity', + 'TeamNotFoundError', + 'Texture', + 'textwidget', + 'ThawMessage', + 'time', + 'TimeFormat', + 'Timer', + 'timer', + 'timestring', + 'TimeType', + 'uicleanupcheck', + 'UIController', + 'UIScale', + 'UISubsystem', + 'UNHANDLED', + 'Vec3', + 'vec3validate', + 'verify_object_death', + 'WeakCall', + 'Widget', + 'widget', + 'WidgetNotFoundError', + 'Window', ] @@ -135,10 +380,12 @@ def _simplify_module_names() -> None: # so let's make an exception for it. if os.environ.get('BA_DOCS_GENERATION', '0') != '1': from efro.util import set_canonical_module + globs = globals() set_canonical_module( module_globals=globs, - names=[n for n in globs.keys() if not n.startswith('_')]) + names=[n for n in globs.keys() if not n.startswith('_')], + ) _simplify_module_names() diff --git a/dist/ba_data/python/ba/_accountv1.py b/dist/ba_data/python/ba/_accountv1.py index d6cf47d..c2b8116 100644 --- a/dist/ba_data/python/ba/_accountv1.py +++ b/dist/ba_data/python/ba/_accountv1.py @@ -40,8 +40,10 @@ class AccountV1Subsystem: # Auto-sign-in to a local account in a moment if we're set to. def do_auto_sign_in() -> None: - if _ba.app.headless_mode or _ba.app.config.get( - 'Auto Account State') == 'Local': + if ( + _ba.app.headless_mode + or _ba.app.config.get('Auto Account State') == 'Local' + ): _internal.sign_in_v1('Local') _ba.pushcall(do_auto_sign_in) @@ -60,9 +62,14 @@ class AccountV1Subsystem: (internal) """ from ba._language import Lstr - _ba.screenmessage(Lstr(resource='getTicketsWindow.receivedTicketsText', - subs=[('${COUNT}', str(count))]), - color=(0, 1, 0)) + + _ba.screenmessage( + Lstr( + resource='getTicketsWindow.receivedTicketsText', + subs=[('${COUNT}', str(count))], + ), + color=(0, 1, 0), + ) _ba.playsound(_ba.getsound('cashRegister')) def cache_league_rank_data(self, data: Any) -> None: @@ -73,9 +80,9 @@ class AccountV1Subsystem: """(internal)""" return self.league_rank_cache.get('info', None) - def get_league_rank_points(self, - data: dict[str, Any] | None, - subset: str | None = None) -> int: + def get_league_rank_points( + self, data: dict[str, Any] | None, subset: str | None = None + ) -> int: """(internal)""" if data is None: return 0 @@ -90,15 +97,23 @@ class AccountV1Subsystem: if ach.complete: total_ach_value += ach.power_ranking_value - trophies_total: int = (data['t0a'] * data['t0am'] + - data['t0b'] * data['t0bm'] + - data['t1'] * data['t1m'] + - data['t2'] * data['t2m'] + - data['t3'] * data['t3m'] + - data['t4'] * data['t4m']) + trophies_total: int = ( + data['t0a'] * data['t0am'] + + data['t0b'] * data['t0bm'] + + data['t1'] * data['t1m'] + + data['t2'] * data['t2m'] + + data['t3'] * data['t3m'] + + data['t4'] * data['t4m'] + ) if subset == 'trophyCount': - val: int = (data['t0a'] + data['t0b'] + data['t1'] + data['t2'] + - data['t3'] + data['t4']) + val: int = ( + data['t0a'] + + data['t0b'] + + data['t1'] + + data['t2'] + + data['t3'] + + data['t4'] + ) assert isinstance(val, int) return val if subset == 'trophies': @@ -108,41 +123,54 @@ class AccountV1Subsystem: raise ValueError('invalid subset value: ' + str(subset)) if data['p']: - pro_mult = 1.0 + float( - _internal.get_v1_account_misc_read_val('proPowerRankingBoost', - 0.0)) * 0.01 + pro_mult = ( + 1.0 + + float( + _internal.get_v1_account_misc_read_val( + 'proPowerRankingBoost', 0.0 + ) + ) + * 0.01 + ) else: pro_mult = 1.0 # For final value, apply our pro mult and activeness-mult. return int( - (total_ach_value + trophies_total) * - (data['act'] if data['act'] is not None else 1.0) * pro_mult) + (total_ach_value + trophies_total) + * (data['act'] if data['act'] is not None else 1.0) + * pro_mult + ) def cache_tournament_info(self, info: Any) -> None: """(internal)""" from ba._generated.enums import TimeType, TimeFormat + for entry in info: - cache_entry = self.tournament_info[entry['tournamentID']] = ( - copy.deepcopy(entry)) + cache_entry = self.tournament_info[ + entry['tournamentID'] + ] = copy.deepcopy(entry) # Also store the time we received this, so we can adjust # time-remaining values/etc. - cache_entry['timeReceived'] = _ba.time(TimeType.REAL, - TimeFormat.MILLISECONDS) + cache_entry['timeReceived'] = _ba.time( + TimeType.REAL, TimeFormat.MILLISECONDS + ) cache_entry['valid'] = True def get_purchased_icons(self) -> list[str]: """(internal)""" # pylint: disable=cyclic-import from ba import _store + if _internal.get_v1_account_state() != 'signed_in': return [] icons = [] store_items = _store.get_store_items() for item_name, item in list(store_items.items()): if item_name.startswith('icons.') and _internal.get_purchased( - item_name): + item_name + ): icons.append(item['icon']) return icons @@ -160,23 +188,28 @@ class AccountV1Subsystem: # If the short version of our account name currently cant be # displayed by the game, cancel. if not _ba.have_chars( - _internal.get_v1_account_display_string(full=False)): + _internal.get_v1_account_display_string(full=False) + ): return config = _ba.app.config - if ('Player Profiles' not in config - or '__account__' not in config['Player Profiles']): + if ( + 'Player Profiles' not in config + or '__account__' not in config['Player Profiles'] + ): # Create a spaz with a nice default purply color. - _internal.add_transaction({ - 'type': 'ADD_PLAYER_PROFILE', - 'name': '__account__', - 'profile': { - 'character': 'Spaz', - 'color': [0.5, 0.25, 1.0], - 'highlight': [0.5, 0.25, 1.0] + _internal.add_transaction( + { + 'type': 'ADD_PLAYER_PROFILE', + 'name': '__account__', + 'profile': { + 'character': 'Spaz', + 'color': [0.5, 0.25, 1.0], + 'highlight': [0.5, 0.25, 1.0], + }, } - }) + ) _internal.run_transactions() def have_pro(self) -> bool: @@ -188,7 +221,8 @@ class AccountV1Subsystem: _internal.get_purchased('upgrades.pro') or _internal.get_purchased('static.pro') or _internal.get_purchased('static.pro_sale') - or 'ballistica' + 'core' == _ba.appname()) + or 'ballistica' + 'core' == _ba.appname() + ) def have_pro_options(self) -> bool: """Return whether pro-options are present. @@ -199,25 +233,34 @@ class AccountV1Subsystem: # We expose pro options if the server tells us to # (which is generally just when we own pro), - # or also if we've been grandfathered in or are using ballistica-core - # builds. + # or also if we've been grandfathered in + # or are using ballistica-core builds. return self.have_pro() or bool( - _internal.get_v1_account_misc_read_val_2('proOptionsUnlocked', - False) - or _ba.app.config.get('lc14292', 0) > 1) + _internal.get_v1_account_misc_read_val_2( + 'proOptionsUnlocked', False + ) + or _ba.app.config.get('lc14292', 0) > 1 + ) def show_post_purchase_message(self) -> None: """(internal)""" from ba._language import Lstr from ba._generated.enums import TimeType + cur_time = _ba.time(TimeType.REAL) - if (self.last_post_purchase_message_time is None - or cur_time - self.last_post_purchase_message_time > 3.0): + if ( + self.last_post_purchase_message_time is None + or cur_time - self.last_post_purchase_message_time > 3.0 + ): self.last_post_purchase_message_time = cur_time with _ba.Context('ui'): - _ba.screenmessage(Lstr(resource='updatingAccountText', - fallback_resource='purchasingText'), - color=(0, 1, 0)) + _ba.screenmessage( + Lstr( + resource='updatingAccountText', + fallback_resource='purchasingText', + ), + color=(0, 1, 0), + ) _ba.playsound(_ba.getsound('click01')) def on_account_state_changed(self) -> None: @@ -225,16 +268,21 @@ class AccountV1Subsystem: from ba._language import Lstr # Run any pending promo codes we had queued up while not signed in. - if _internal.get_v1_account_state( - ) == 'signed_in' and self.pending_promo_codes: + if ( + _internal.get_v1_account_state() == 'signed_in' + and self.pending_promo_codes + ): for code in self.pending_promo_codes: - _ba.screenmessage(Lstr(resource='submittingPromoCodeText'), - color=(0, 1, 0)) - _internal.add_transaction({ - 'type': 'PROMO_CODE', - 'expire_time': time.time() + 5, - 'code': code - }) + _ba.screenmessage( + Lstr(resource='submittingPromoCodeText'), color=(0, 1, 0) + ) + _internal.add_transaction( + { + 'type': 'PROMO_CODE', + 'expire_time': time.time() + 5, + 'code': code, + } + ) _internal.run_transactions() self.pending_promo_codes = [] @@ -254,18 +302,18 @@ class AccountV1Subsystem: # If we're still not signed in and have pending codes, # inform the user that they need to sign in to use them. if self.pending_promo_codes: - _ba.screenmessage(Lstr(resource='signInForPromoCodeText'), - color=(1, 0, 0)) + _ba.screenmessage( + Lstr(resource='signInForPromoCodeText'), color=(1, 0, 0) + ) _ba.playsound(_ba.getsound('error')) self.pending_promo_codes.append(code) _ba.timer(6.0, check_pending_codes, timetype=TimeType.REAL) return - _ba.screenmessage(Lstr(resource='submittingPromoCodeText'), - color=(0, 1, 0)) - _internal.add_transaction({ - 'type': 'PROMO_CODE', - 'expire_time': time.time() + 5, - 'code': code - }) + _ba.screenmessage( + Lstr(resource='submittingPromoCodeText'), color=(0, 1, 0) + ) + _internal.add_transaction( + {'type': 'PROMO_CODE', 'expire_time': time.time() + 5, 'code': code} + ) _internal.run_transactions() diff --git a/dist/ba_data/python/ba/_accountv2.py b/dist/ba_data/python/ba/_accountv2.py index b5758ff..77b4ecd 100644 --- a/dist/ba_data/python/ba/_accountv2.py +++ b/dist/ba_data/python/ba/_accountv2.py @@ -9,7 +9,7 @@ from typing import TYPE_CHECKING import _ba if TYPE_CHECKING: - pass + from typing import Any class AccountV2Subsystem: @@ -55,8 +55,9 @@ class AccountV2Subsystem: """Internal - should be overridden by subclass.""" return None - def on_primary_account_changed(self, - account: AccountV2Handle | None) -> None: + def on_primary_account_changed( + self, account: AccountV2Handle | None + ) -> None: """Callback run after the primary account changes. Will be called with None on log-outs or when new credentials @@ -70,13 +71,17 @@ class AccountV2Subsystem: # informed when that process completes. if account.workspaceid is not None: assert account.workspacename is not None - if (not self._initial_login_completed - and not self._kicked_off_workspace_load): + if ( + not self._initial_login_completed + and not self._kicked_off_workspace_load + ): self._kicked_off_workspace_load = True _ba.app.workspaces.set_active_workspace( + account=account, workspaceid=account.workspaceid, workspacename=account.workspacename, - on_completed=self._on_set_active_workspace_completed) + on_completed=self._on_set_active_workspace_completed, + ) else: # Don't activate workspaces if we've already told the game # that initial-log-in is done or if we've already kicked @@ -84,7 +89,8 @@ class AccountV2Subsystem: _ba.screenmessage( f'\'{account.workspacename}\'' f' will be activated at next app launch.', - color=(1, 1, 0)) + color=(1, 1, 0), + ) _ba.playsound(_ba.getsound('error')) return @@ -111,10 +117,20 @@ class AccountV2Subsystem: class AccountV2Handle: - """Handle for interacting with a v2 account.""" + """Handle for interacting with a V2 account. + + This class supports the 'with' statement, which is how it is + used with some operations such as cloud messaging. + """ def __init__(self) -> None: self.tag = '?' self.workspacename: str | None = None self.workspaceid: str | None = None + + def __enter__(self) -> None: + """Support for "with" statement.""" + + def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> Any: + """Support for "with" statement.""" diff --git a/dist/ba_data/python/ba/_achievement.py b/dist/ba_data/python/ba/_achievement.py index 006e0c7..c666e3f 100644 --- a/dist/ba_data/python/ba/_achievement.py +++ b/dist/ba_data/python/ba/_achievement.py @@ -59,7 +59,7 @@ ACH_LEVEL_NAMES = { 'Uber Football Shutout': 'Uber Football', 'Uber Football Victory': 'Uber Football', 'Uber Onslaught Victory': 'Uber Onslaught', - 'Uber Runaround Victory': 'Uber Runaround' + 'Uber Runaround Victory': 'Uber Runaround', } @@ -86,230 +86,425 @@ class AchievementSubsystem: # 5 achs.append( - Achievement('In Control', 'achievementInControl', (1, 1, 1), '', - 5)) + Achievement('In Control', 'achievementInControl', (1, 1, 1), '', 5) + ) # 15 achs.append( - Achievement('Sharing is Caring', 'achievementSharingIsCaring', - (1, 1, 1), '', 15)) + Achievement( + 'Sharing is Caring', + 'achievementSharingIsCaring', + (1, 1, 1), + '', + 15, + ) + ) # 10 achs.append( - Achievement('Dual Wielding', 'achievementDualWielding', (1, 1, 1), - '', 10)) + Achievement( + 'Dual Wielding', 'achievementDualWielding', (1, 1, 1), '', 10 + ) + ) # 10 achs.append( - Achievement('Free Loader', 'achievementFreeLoader', (1, 1, 1), '', - 10)) + Achievement( + 'Free Loader', 'achievementFreeLoader', (1, 1, 1), '', 10 + ) + ) # 20 achs.append( - Achievement('Team Player', 'achievementTeamPlayer', (1, 1, 1), '', - 20)) + Achievement( + 'Team Player', 'achievementTeamPlayer', (1, 1, 1), '', 20 + ) + ) # 5 achs.append( - Achievement('Onslaught Training Victory', 'achievementOnslaught', - (1, 1, 1), 'Default:Onslaught Training', 5)) + Achievement( + 'Onslaught Training Victory', + 'achievementOnslaught', + (1, 1, 1), + 'Default:Onslaught Training', + 5, + ) + ) # 5 achs.append( - Achievement('Off You Go Then', 'achievementOffYouGo', - (1, 1.1, 1.3), 'Default:Onslaught Training', 5)) + Achievement( + 'Off You Go Then', + 'achievementOffYouGo', + (1, 1.1, 1.3), + 'Default:Onslaught Training', + 5, + ) + ) # 10 achs.append( - Achievement('Boxer', - 'achievementBoxer', (1, 0.6, 0.6), - 'Default:Onslaught Training', - 10, - hard_mode_only=True)) + Achievement( + 'Boxer', + 'achievementBoxer', + (1, 0.6, 0.6), + 'Default:Onslaught Training', + 10, + hard_mode_only=True, + ) + ) # 10 achs.append( - Achievement('Rookie Onslaught Victory', 'achievementOnslaught', - (0.5, 1.4, 0.6), 'Default:Rookie Onslaught', 10)) + Achievement( + 'Rookie Onslaught Victory', + 'achievementOnslaught', + (0.5, 1.4, 0.6), + 'Default:Rookie Onslaught', + 10, + ) + ) # 10 achs.append( - Achievement('Mine Games', 'achievementMine', (1, 1, 1.4), - 'Default:Rookie Onslaught', 10)) + Achievement( + 'Mine Games', + 'achievementMine', + (1, 1, 1.4), + 'Default:Rookie Onslaught', + 10, + ) + ) # 15 achs.append( - Achievement('Flawless Victory', - 'achievementFlawlessVictory', (1, 1, 1), - 'Default:Rookie Onslaught', - 15, - hard_mode_only=True)) + Achievement( + 'Flawless Victory', + 'achievementFlawlessVictory', + (1, 1, 1), + 'Default:Rookie Onslaught', + 15, + hard_mode_only=True, + ) + ) # 10 achs.append( - Achievement('Rookie Football Victory', - 'achievementFootballVictory', (1.0, 1, 0.6), - 'Default:Rookie Football', 10)) + Achievement( + 'Rookie Football Victory', + 'achievementFootballVictory', + (1.0, 1, 0.6), + 'Default:Rookie Football', + 10, + ) + ) # 10 achs.append( - Achievement('Super Punch', 'achievementSuperPunch', (1, 1, 1.8), - 'Default:Rookie Football', 10)) + Achievement( + 'Super Punch', + 'achievementSuperPunch', + (1, 1, 1.8), + 'Default:Rookie Football', + 10, + ) + ) # 15 achs.append( - Achievement('Rookie Football Shutout', - 'achievementFootballShutout', (1, 1, 1), - 'Default:Rookie Football', - 15, - hard_mode_only=True)) + Achievement( + 'Rookie Football Shutout', + 'achievementFootballShutout', + (1, 1, 1), + 'Default:Rookie Football', + 15, + hard_mode_only=True, + ) + ) # 15 achs.append( - Achievement('Pro Onslaught Victory', 'achievementOnslaught', - (0.3, 1, 2.0), 'Default:Pro Onslaught', 15)) + Achievement( + 'Pro Onslaught Victory', + 'achievementOnslaught', + (0.3, 1, 2.0), + 'Default:Pro Onslaught', + 15, + ) + ) # 15 achs.append( - Achievement('Boom Goes the Dynamite', 'achievementTNT', - (1.4, 1.2, 0.8), 'Default:Pro Onslaught', 15)) + Achievement( + 'Boom Goes the Dynamite', + 'achievementTNT', + (1.4, 1.2, 0.8), + 'Default:Pro Onslaught', + 15, + ) + ) # 20 achs.append( - Achievement('Pro Boxer', - 'achievementBoxer', (2, 2, 0), - 'Default:Pro Onslaught', - 20, - hard_mode_only=True)) + Achievement( + 'Pro Boxer', + 'achievementBoxer', + (2, 2, 0), + 'Default:Pro Onslaught', + 20, + hard_mode_only=True, + ) + ) # 15 achs.append( - Achievement('Pro Football Victory', 'achievementFootballVictory', - (1.3, 1.3, 2.0), 'Default:Pro Football', 15)) + Achievement( + 'Pro Football Victory', + 'achievementFootballVictory', + (1.3, 1.3, 2.0), + 'Default:Pro Football', + 15, + ) + ) # 15 achs.append( - Achievement('Super Mega Punch', 'achievementSuperPunch', - (2, 1, 0.6), 'Default:Pro Football', 15)) + Achievement( + 'Super Mega Punch', + 'achievementSuperPunch', + (2, 1, 0.6), + 'Default:Pro Football', + 15, + ) + ) # 20 achs.append( - Achievement('Pro Football Shutout', - 'achievementFootballShutout', (0.7, 0.7, 2.0), - 'Default:Pro Football', - 20, - hard_mode_only=True)) + Achievement( + 'Pro Football Shutout', + 'achievementFootballShutout', + (0.7, 0.7, 2.0), + 'Default:Pro Football', + 20, + hard_mode_only=True, + ) + ) # 15 achs.append( - Achievement('Pro Runaround Victory', 'achievementRunaround', - (1, 1, 1), 'Default:Pro Runaround', 15)) + Achievement( + 'Pro Runaround Victory', + 'achievementRunaround', + (1, 1, 1), + 'Default:Pro Runaround', + 15, + ) + ) # 20 achs.append( - Achievement('Precision Bombing', - 'achievementCrossHair', (1, 1, 1.3), - 'Default:Pro Runaround', - 20, - hard_mode_only=True)) + Achievement( + 'Precision Bombing', + 'achievementCrossHair', + (1, 1, 1.3), + 'Default:Pro Runaround', + 20, + hard_mode_only=True, + ) + ) # 25 achs.append( - Achievement('The Wall', - 'achievementWall', (1, 0.7, 0.7), - 'Default:Pro Runaround', - 25, - hard_mode_only=True)) + Achievement( + 'The Wall', + 'achievementWall', + (1, 0.7, 0.7), + 'Default:Pro Runaround', + 25, + hard_mode_only=True, + ) + ) # 30 achs.append( - Achievement('Uber Onslaught Victory', 'achievementOnslaught', - (2, 2, 1), 'Default:Uber Onslaught', 30)) + Achievement( + 'Uber Onslaught Victory', + 'achievementOnslaught', + (2, 2, 1), + 'Default:Uber Onslaught', + 30, + ) + ) # 30 achs.append( - Achievement('Gold Miner', - 'achievementMine', (2, 1.6, 0.2), - 'Default:Uber Onslaught', - 30, - hard_mode_only=True)) + Achievement( + 'Gold Miner', + 'achievementMine', + (2, 1.6, 0.2), + 'Default:Uber Onslaught', + 30, + hard_mode_only=True, + ) + ) # 30 achs.append( - Achievement('TNT Terror', - 'achievementTNT', (2, 1.8, 0.3), - 'Default:Uber Onslaught', - 30, - hard_mode_only=True)) + Achievement( + 'TNT Terror', + 'achievementTNT', + (2, 1.8, 0.3), + 'Default:Uber Onslaught', + 30, + hard_mode_only=True, + ) + ) # 30 achs.append( - Achievement('Uber Football Victory', 'achievementFootballVictory', - (1.8, 1.4, 0.3), 'Default:Uber Football', 30)) + Achievement( + 'Uber Football Victory', + 'achievementFootballVictory', + (1.8, 1.4, 0.3), + 'Default:Uber Football', + 30, + ) + ) # 30 achs.append( - Achievement('Got the Moves', - 'achievementGotTheMoves', (2, 1, 0), - 'Default:Uber Football', - 30, - hard_mode_only=True)) + Achievement( + 'Got the Moves', + 'achievementGotTheMoves', + (2, 1, 0), + 'Default:Uber Football', + 30, + hard_mode_only=True, + ) + ) # 40 achs.append( - Achievement('Uber Football Shutout', - 'achievementFootballShutout', (2, 2, 0), - 'Default:Uber Football', - 40, - hard_mode_only=True)) + Achievement( + 'Uber Football Shutout', + 'achievementFootballShutout', + (2, 2, 0), + 'Default:Uber Football', + 40, + hard_mode_only=True, + ) + ) # 30 achs.append( - Achievement('Uber Runaround Victory', 'achievementRunaround', - (1.5, 1.2, 0.2), 'Default:Uber Runaround', 30)) + Achievement( + 'Uber Runaround Victory', + 'achievementRunaround', + (1.5, 1.2, 0.2), + 'Default:Uber Runaround', + 30, + ) + ) # 40 achs.append( - Achievement('The Great Wall', - 'achievementWall', (2, 1.7, 0.4), - 'Default:Uber Runaround', - 40, - hard_mode_only=True)) + Achievement( + 'The Great Wall', + 'achievementWall', + (2, 1.7, 0.4), + 'Default:Uber Runaround', + 40, + hard_mode_only=True, + ) + ) # 40 achs.append( - Achievement('Stayin\' Alive', - 'achievementStayinAlive', (2, 2, 1), - 'Default:Uber Runaround', - 40, - hard_mode_only=True)) + Achievement( + 'Stayin\' Alive', + 'achievementStayinAlive', + (2, 2, 1), + 'Default:Uber Runaround', + 40, + hard_mode_only=True, + ) + ) # 20 achs.append( - Achievement('Last Stand Master', - 'achievementMedalSmall', (2, 1.5, 0.3), - 'Default:The Last Stand', - 20, - hard_mode_only=True)) + Achievement( + 'Last Stand Master', + 'achievementMedalSmall', + (2, 1.5, 0.3), + 'Default:The Last Stand', + 20, + hard_mode_only=True, + ) + ) # 40 achs.append( - Achievement('Last Stand Wizard', - 'achievementMedalMedium', (2, 1.5, 0.3), - 'Default:The Last Stand', - 40, - hard_mode_only=True)) + Achievement( + 'Last Stand Wizard', + 'achievementMedalMedium', + (2, 1.5, 0.3), + 'Default:The Last Stand', + 40, + hard_mode_only=True, + ) + ) # 60 achs.append( - Achievement('Last Stand God', - 'achievementMedalLarge', (2, 1.5, 0.3), - 'Default:The Last Stand', - 60, - hard_mode_only=True)) + Achievement( + 'Last Stand God', + 'achievementMedalLarge', + (2, 1.5, 0.3), + 'Default:The Last Stand', + 60, + hard_mode_only=True, + ) + ) # 5 achs.append( - Achievement('Onslaught Master', 'achievementMedalSmall', - (0.7, 1, 0.7), 'Challenges:Infinite Onslaught', 5)) + Achievement( + 'Onslaught Master', + 'achievementMedalSmall', + (0.7, 1, 0.7), + 'Challenges:Infinite Onslaught', + 5, + ) + ) # 15 achs.append( - Achievement('Onslaught Wizard', 'achievementMedalMedium', - (0.7, 1.0, 0.7), 'Challenges:Infinite Onslaught', 15)) + Achievement( + 'Onslaught Wizard', + 'achievementMedalMedium', + (0.7, 1.0, 0.7), + 'Challenges:Infinite Onslaught', + 15, + ) + ) # 30 achs.append( - Achievement('Onslaught God', 'achievementMedalLarge', - (0.7, 1.0, 0.7), 'Challenges:Infinite Onslaught', 30)) + Achievement( + 'Onslaught God', + 'achievementMedalLarge', + (0.7, 1.0, 0.7), + 'Challenges:Infinite Onslaught', + 30, + ) + ) # 5 achs.append( - Achievement('Runaround Master', 'achievementMedalSmall', - (1.0, 1.0, 1.2), 'Challenges:Infinite Runaround', 5)) + Achievement( + 'Runaround Master', + 'achievementMedalSmall', + (1.0, 1.0, 1.2), + 'Challenges:Infinite Runaround', + 5, + ) + ) # 15 achs.append( - Achievement('Runaround Wizard', 'achievementMedalMedium', - (1.0, 1.0, 1.2), 'Challenges:Infinite Runaround', 15)) + Achievement( + 'Runaround Wizard', + 'achievementMedalMedium', + (1.0, 1.0, 1.2), + 'Challenges:Infinite Runaround', + 15, + ) + ) # 30 achs.append( - Achievement('Runaround God', 'achievementMedalLarge', - (1.0, 1.0, 1.2), 'Challenges:Infinite Runaround', 30)) + Achievement( + 'Runaround God', + 'achievementMedalLarge', + (1.0, 1.0, 1.2), + 'Challenges:Infinite Runaround', + 30, + ) + ) def award_local_achievement(self, achname: str) -> None: """For non-game-based achievements such as controller-connection.""" @@ -321,10 +516,9 @@ class AchievementSubsystem: _internal.report_achievement(achname) # And to our account. - _internal.add_transaction({ - 'type': 'ACHIEVEMENT', - 'name': achname - }) + _internal.add_transaction( + {'type': 'ACHIEVEMENT', 'name': achname} + ) # Now attempt to show a banner. self.display_achievement_banner(achname) @@ -377,17 +571,17 @@ class AchievementSubsystem: raise ValueError("Invalid achievement name: '" + name + "'") return achs[0] - def achievements_for_coop_level(self, - level_name: str) -> list[Achievement]: + def achievements_for_coop_level(self, level_name: str) -> list[Achievement]: """Given a level name, return achievements available for it.""" # For the Easy campaign we return achievements for the Default # campaign too. (want the user to see what achievements are part of the # level even if they can't unlock them all on easy mode). return [ - a for a in self.achievements - if a.level_name in (level_name, - level_name.replace('Easy', 'Default')) + a + for a in self.achievements + if a.level_name + in (level_name, level_name.replace('Easy', 'Default')) ] def _test(self) -> None: @@ -443,13 +637,15 @@ class Achievement: Category: **App Classes** """ - def __init__(self, - name: str, - icon_name: str, - icon_color: Sequence[float], - level_name: str, - award: int, - hard_mode_only: bool = False): + def __init__( + self, + name: str, + icon_name: str, + icon_color: Sequence[float], + level_name: str, + award: int, + hard_mode_only: bool = False, + ): self._name = name self._icon_name = icon_name self._icon_color: Sequence[float] = list(icon_color) + [1] @@ -471,7 +667,8 @@ class Achievement: def get_icon_texture(self, complete: bool) -> ba.Texture: """Return the icon texture to display for this achievement""" return _ba.gettexture( - self._icon_name if complete else 'achievementEmpty') + self._icon_name if complete else 'achievementEmpty' + ) def get_icon_color(self, complete: bool) -> Sequence[float]: """Return the color tint for this Achievement's icon.""" @@ -494,6 +691,7 @@ class Achievement: def announce_completion(self, sound: bool = True) -> None: """Kick off an announcement for this achievement's completion.""" from ba._generated.enums import TimeType + app = _ba.app # Even though there are technically achievements when we're not @@ -512,14 +710,17 @@ class Achievement: # Need to check last time too; its possible our timer wasn't able to # clear itself if an activity died and took it down with it. - if ((app.ach.achievement_display_timer is None - or _ba.time(TimeType.REAL) - app.ach.last_achievement_display_time - > 2.0) and _ba.getactivity(doraise=False) is not None): + if ( + app.ach.achievement_display_timer is None + or _ba.time(TimeType.REAL) - app.ach.last_achievement_display_time + > 2.0 + ) and _ba.getactivity(doraise=False) is not None: app.ach.achievement_display_timer = _ba.Timer( 1.0, _display_next_achievement, repeat=True, - timetype=TimeType.BASE) + timetype=TimeType.BASE, + ) # Show the first immediately. _display_next_achievement() @@ -538,27 +739,37 @@ class Achievement: def display_name(self) -> ba.Lstr: """Return a ba.Lstr for this Achievement's name.""" from ba._language import Lstr + name: ba.Lstr | str try: if self._level_name != '': from ba._campaign import getcampaign + campaignname, campaign_level = self._level_name.split(':') - name = getcampaign(campaignname).getlevel( - campaign_level).displayname + name = ( + getcampaign(campaignname) + .getlevel(campaign_level) + .displayname + ) else: name = '' except Exception: name = '' print_exception() - return Lstr(resource='achievements.' + self._name + '.name', - subs=[('${LEVEL}', name)]) + return Lstr( + resource='achievements.' + self._name + '.name', + subs=[('${LEVEL}', name)], + ) @property def description(self) -> ba.Lstr: """Get a ba.Lstr for the Achievement's brief description.""" from ba._language import Lstr - if 'description' in _ba.app.lang.get_resource('achievements')[ - self._name]: + + if ( + 'description' + in _ba.app.lang.get_resource('achievements')[self._name] + ): return Lstr(resource='achievements.' + self._name + '.description') return Lstr(resource='achievements.' + self._name + '.descriptionFull') @@ -566,12 +777,17 @@ class Achievement: def description_complete(self) -> ba.Lstr: """Get a ba.Lstr for the Achievement's description when completed.""" from ba._language import Lstr - if 'descriptionComplete' in _ba.app.lang.get_resource('achievements')[ - self._name]: - return Lstr(resource='achievements.' + self._name + - '.descriptionComplete') - return Lstr(resource='achievements.' + self._name + - '.descriptionFullComplete') + + if ( + 'descriptionComplete' + in _ba.app.lang.get_resource('achievements')[self._name] + ): + return Lstr( + resource='achievements.' + self._name + '.descriptionComplete' + ) + return Lstr( + resource='achievements.' + self._name + '.descriptionFullComplete' + ) @property def description_full(self) -> ba.Lstr: @@ -580,25 +796,44 @@ class Achievement: return Lstr( resource='achievements.' + self._name + '.descriptionFull', - subs=[('${LEVEL}', - Lstr(translate=('coopLevelNames', - ACH_LEVEL_NAMES.get(self._name, '?'))))]) + subs=[ + ( + '${LEVEL}', + Lstr( + translate=( + 'coopLevelNames', + ACH_LEVEL_NAMES.get(self._name, '?'), + ) + ), + ) + ], + ) @property def description_full_complete(self) -> ba.Lstr: """Get a ba.Lstr for the Achievement's full desc. when completed.""" from ba._language import Lstr + return Lstr( resource='achievements.' + self._name + '.descriptionFullComplete', - subs=[('${LEVEL}', - Lstr(translate=('coopLevelNames', - ACH_LEVEL_NAMES.get(self._name, '?'))))]) + subs=[ + ( + '${LEVEL}', + Lstr( + translate=( + 'coopLevelNames', + ACH_LEVEL_NAMES.get(self._name, '?'), + ) + ), + ) + ], + ) def get_award_ticket_value(self, include_pro_bonus: bool = False) -> int: """Get the ticket award value for this achievement.""" - val: int = (_internal.get_v1_account_misc_read_val( - 'achAward.' + self._name, self._award) * - _get_ach_mult(include_pro_bonus)) + val: int = _internal.get_v1_account_misc_read_val( + 'achAward.' + self._name, self._award + ) * _get_ach_mult(include_pro_bonus) assert isinstance(val, int) return val @@ -606,17 +841,20 @@ class Achievement: def power_ranking_value(self) -> int: """Get the power-ranking award value for this achievement.""" val: int = _internal.get_v1_account_misc_read_val( - 'achLeaguePoints.' + self._name, self._award) + 'achLeaguePoints.' + self._name, self._award + ) assert isinstance(val, int) return val - def create_display(self, - x: float, - y: float, - delay: float, - outdelay: float | None = None, - color: Sequence[float] | None = None, - style: str = 'post_game') -> list[ba.Actor]: + def create_display( + self, + x: float, + y: float, + delay: float, + outdelay: float | None = None, + color: Sequence[float] | None = None, + style: str = 'post_game', + ) -> list[ba.Actor]: """Create a display for the Achievement. Shows the Achievement icon, name, and description. @@ -660,7 +898,7 @@ class Achievement: if isinstance(session, CoopSession): campaign = session.campaign assert campaign is not None - hmo = (self._hard_mode_only and campaign.name == 'Easy') + hmo = self._hard_mode_only and campaign.name == 'Easy' else: hmo = False except Exception: @@ -671,11 +909,9 @@ class Achievement: if in_game_colors: objs = [] - out_delay_fin = (delay + - outdelay) if outdelay is not None else None + out_delay_fin = (delay + outdelay) if outdelay is not None else None if color is not None: - cl1 = (2.0 * color[0], 2.0 * color[1], 2.0 * color[2], - color[3]) + cl1 = (2.0 * color[0], 2.0 * color[1], 2.0 * color[2], color[3]) cl2 = color else: cl1 = (1.5, 1.5, 2, 1.0) @@ -686,51 +922,59 @@ class Achievement: cl2 = (cl2[0], cl2[1], cl2[2], cl2[3] * 0.2) objs.append( - Image(self.get_icon_texture(False), - host_only=True, - color=cl1, - position=(x - 25, y + 5), - attach=attach, - transition=Image.Transition.FADE_IN, - transition_delay=delay, - vr_depth=4, - transition_out_delay=out_delay_fin, - scale=(40, 40)).autoretain()) + Image( + self.get_icon_texture(False), + host_only=True, + color=cl1, + position=(x - 25, y + 5), + attach=attach, + transition=Image.Transition.FADE_IN, + transition_delay=delay, + vr_depth=4, + transition_out_delay=out_delay_fin, + scale=(40, 40), + ).autoretain() + ) txt = self.display_name txt_s = 0.85 txt_max_w = 300 objs.append( - Text(txt, - host_only=True, - maxwidth=txt_max_w, - position=(x, y + 2), - transition=Text.Transition.FADE_IN, - scale=txt_s, - flatness=0.6, - shadow=0.5, - h_attach=h_attach, - v_attach=v_attach, - color=cl2, - transition_delay=delay + 0.05, - transition_out_delay=out_delay_fin).autoretain()) + Text( + txt, + host_only=True, + maxwidth=txt_max_w, + position=(x, y + 2), + transition=Text.Transition.FADE_IN, + scale=txt_s, + flatness=0.6, + shadow=0.5, + h_attach=h_attach, + v_attach=v_attach, + color=cl2, + transition_delay=delay + 0.05, + transition_out_delay=out_delay_fin, + ).autoretain() + ) txt2_s = 0.62 txt2_max_w = 400 objs.append( - Text(self.description_full - if in_main_menu else self.description, - host_only=True, - maxwidth=txt2_max_w, - position=(x, y - 14), - transition=Text.Transition.FADE_IN, - vr_depth=-5, - h_attach=h_attach, - v_attach=v_attach, - scale=txt2_s, - flatness=1.0, - shadow=0.5, - color=cl2, - transition_delay=delay + 0.1, - transition_out_delay=out_delay_fin).autoretain()) + Text( + self.description_full if in_main_menu else self.description, + host_only=True, + maxwidth=txt2_max_w, + position=(x, y - 14), + transition=Text.Transition.FADE_IN, + vr_depth=-5, + h_attach=h_attach, + v_attach=v_attach, + scale=txt2_s, + flatness=1.0, + shadow=0.5, + color=cl2, + transition_delay=delay + 0.1, + transition_out_delay=out_delay_fin, + ).autoretain() + ) if hmo: txtactor = Text( @@ -749,39 +993,46 @@ class Achievement: shadow=0.5, color=(1, 1, 0.6, 1), transition_delay=delay + 0.1, - transition_out_delay=out_delay_fin).autoretain() + transition_out_delay=out_delay_fin, + ).autoretain() txtactor.node.rotate = 10 objs.append(txtactor) # Ticket-award. award_x = -100 objs.append( - Text(_ba.charstr(SpecialChar.TICKET), - host_only=True, - position=(x + award_x + 33, y + 7), - transition=Text.Transition.FADE_IN, - scale=1.5, - h_attach=h_attach, - v_attach=v_attach, - h_align=Text.HAlign.CENTER, - v_align=Text.VAlign.CENTER, - color=(1, 1, 1, 0.2 if hmo else 0.4), - transition_delay=delay + 0.05, - transition_out_delay=out_delay_fin).autoretain()) + Text( + _ba.charstr(SpecialChar.TICKET), + host_only=True, + position=(x + award_x + 33, y + 7), + transition=Text.Transition.FADE_IN, + scale=1.5, + h_attach=h_attach, + v_attach=v_attach, + h_align=Text.HAlign.CENTER, + v_align=Text.VAlign.CENTER, + color=(1, 1, 1, 0.2 if hmo else 0.4), + transition_delay=delay + 0.05, + transition_out_delay=out_delay_fin, + ).autoretain() + ) objs.append( - Text('+' + str(self.get_award_ticket_value()), - host_only=True, - position=(x + award_x + 28, y + 16), - transition=Text.Transition.FADE_IN, - scale=0.7, - flatness=1, - h_attach=h_attach, - v_attach=v_attach, - h_align=Text.HAlign.CENTER, - v_align=Text.VAlign.CENTER, - color=cl2, - transition_delay=delay + 0.05, - transition_out_delay=out_delay_fin).autoretain()) + Text( + '+' + str(self.get_award_ticket_value()), + host_only=True, + position=(x + award_x + 28, y + 16), + transition=Text.Transition.FADE_IN, + scale=0.7, + flatness=1, + h_attach=h_attach, + v_attach=v_attach, + h_align=Text.HAlign.CENTER, + v_align=Text.VAlign.CENTER, + color=cl2, + transition_delay=delay + 0.05, + transition_out_delay=out_delay_fin, + ).autoretain() + ) else: complete = self.complete @@ -790,61 +1041,77 @@ class Achievement: if hmo and not complete: c_icon = (c_icon[0], c_icon[1], c_icon[2], c_icon[3] * 0.3) objs.append( - Image(self.get_icon_texture(complete), - host_only=True, - color=c_icon, - position=(x - 25, y + 5), - attach=attach, - vr_depth=4, - transition=Image.Transition.IN_RIGHT, - transition_delay=delay, - transition_out_delay=None, - scale=(40, 40)).autoretain()) + Image( + self.get_icon_texture(complete), + host_only=True, + color=c_icon, + position=(x - 25, y + 5), + attach=attach, + vr_depth=4, + transition=Image.Transition.IN_RIGHT, + transition_delay=delay, + transition_out_delay=None, + scale=(40, 40), + ).autoretain() + ) if complete: objs.append( - Image(_ba.gettexture('achievementOutline'), - host_only=True, - model_transparent=_ba.getmodel('achievementOutline'), - color=(2, 1.4, 0.4, 1), - vr_depth=8, - position=(x - 25, y + 5), - attach=attach, - transition=Image.Transition.IN_RIGHT, - transition_delay=delay, - transition_out_delay=None, - scale=(40, 40)).autoretain()) + Image( + _ba.gettexture('achievementOutline'), + host_only=True, + model_transparent=_ba.getmodel('achievementOutline'), + color=(2, 1.4, 0.4, 1), + vr_depth=8, + position=(x - 25, y + 5), + attach=attach, + transition=Image.Transition.IN_RIGHT, + transition_delay=delay, + transition_out_delay=None, + scale=(40, 40), + ).autoretain() + ) else: if not complete: award_x = -100 objs.append( - Text(_ba.charstr(SpecialChar.TICKET), - host_only=True, - position=(x + award_x + 33, y + 7), - transition=Text.Transition.IN_RIGHT, - scale=1.5, - h_attach=h_attach, - v_attach=v_attach, - h_align=Text.HAlign.CENTER, - v_align=Text.VAlign.CENTER, - color=(1, 1, 1, 0.4) if complete else - (1, 1, 1, (0.1 if hmo else 0.2)), - transition_delay=delay + 0.05, - transition_out_delay=None).autoretain()) + Text( + _ba.charstr(SpecialChar.TICKET), + host_only=True, + position=(x + award_x + 33, y + 7), + transition=Text.Transition.IN_RIGHT, + scale=1.5, + h_attach=h_attach, + v_attach=v_attach, + h_align=Text.HAlign.CENTER, + v_align=Text.VAlign.CENTER, + color=(1, 1, 1, 0.4) + if complete + else (1, 1, 1, (0.1 if hmo else 0.2)), + transition_delay=delay + 0.05, + transition_out_delay=None, + ).autoretain() + ) objs.append( - Text('+' + str(self.get_award_ticket_value()), - host_only=True, - position=(x + award_x + 28, y + 16), - transition=Text.Transition.IN_RIGHT, - scale=0.7, - flatness=1, - h_attach=h_attach, - v_attach=v_attach, - h_align=Text.HAlign.CENTER, - v_align=Text.VAlign.CENTER, - color=((0.8, 0.93, 0.8, 1.0) if complete else - (0.6, 0.6, 0.6, (0.2 if hmo else 0.4))), - transition_delay=delay + 0.05, - transition_out_delay=None).autoretain()) + Text( + '+' + str(self.get_award_ticket_value()), + host_only=True, + position=(x + award_x + 28, y + 16), + transition=Text.Transition.IN_RIGHT, + scale=0.7, + flatness=1, + h_attach=h_attach, + v_attach=v_attach, + h_align=Text.HAlign.CENTER, + v_align=Text.VAlign.CENTER, + color=( + (0.8, 0.93, 0.8, 1.0) + if complete + else (0.6, 0.6, 0.6, (0.2 if hmo else 0.4)) + ), + transition_delay=delay + 0.05, + transition_out_delay=None, + ).autoretain() + ) # Show 'hard-mode-only' only over incomplete achievements # when that's the case. @@ -865,41 +1132,53 @@ class Achievement: shadow=0.5, color=(1, 1, 0.6, 1), transition_delay=delay + 0.05, - transition_out_delay=None).autoretain() + transition_out_delay=None, + ).autoretain() assert txtactor.node txtactor.node.rotate = 10 objs.append(txtactor) objs.append( - Text(self.display_name, - host_only=True, - maxwidth=300, - position=(x, y + 2), - transition=Text.Transition.IN_RIGHT, - scale=0.85, - flatness=0.6, - h_attach=h_attach, - v_attach=v_attach, - color=((0.8, 0.93, 0.8, 1.0) if complete else - (0.6, 0.6, 0.6, (0.2 if hmo else 0.4))), - transition_delay=delay + 0.05, - transition_out_delay=None).autoretain()) + Text( + self.display_name, + host_only=True, + maxwidth=300, + position=(x, y + 2), + transition=Text.Transition.IN_RIGHT, + scale=0.85, + flatness=0.6, + h_attach=h_attach, + v_attach=v_attach, + color=( + (0.8, 0.93, 0.8, 1.0) + if complete + else (0.6, 0.6, 0.6, (0.2 if hmo else 0.4)) + ), + transition_delay=delay + 0.05, + transition_out_delay=None, + ).autoretain() + ) objs.append( - Text(self.description_complete - if complete else self.description, - host_only=True, - maxwidth=400, - position=(x, y - 14), - transition=Text.Transition.IN_RIGHT, - vr_depth=-5, - h_attach=h_attach, - v_attach=v_attach, - scale=0.62, - flatness=1.0, - color=((0.6, 0.6, 0.6, 1.0) if complete else - (0.6, 0.6, 0.6, (0.2 if hmo else 0.4))), - transition_delay=delay + 0.1, - transition_out_delay=None).autoretain()) + Text( + self.description_complete if complete else self.description, + host_only=True, + maxwidth=400, + position=(x, y - 14), + transition=Text.Transition.IN_RIGHT, + vr_depth=-5, + h_attach=h_attach, + v_attach=v_attach, + scale=0.62, + flatness=1.0, + color=( + (0.6, 0.6, 0.6, 1.0) + if complete + else (0.6, 0.6, 0.6, (0.2 if hmo else 0.4)) + ), + transition_delay=delay + 0.1, + transition_out_delay=None, + ).autoretain() + ) return objs def _getconfig(self) -> dict[str, Any]: @@ -907,15 +1186,17 @@ class Achievement: Return the sub-dict in settings where this achievement's state is stored, creating it if need be. """ - val: dict[str, Any] = (_ba.app.config.setdefault( - 'Achievements', {}).setdefault(self._name, {'Complete': False})) + val: dict[str, Any] = _ba.app.config.setdefault( + 'Achievements', {} + ).setdefault(self._name, {'Complete': False}) assert isinstance(val, dict) return val def _remove_banner_slot(self) -> None: assert self._completion_banner_slot is not None _ba.app.ach.achievement_completion_banner_slots.remove( - self._completion_banner_slot) + self._completion_banner_slot + ) self._completion_banner_slot = None def show_completion_banner(self, sound: bool = True) -> None: @@ -927,6 +1208,7 @@ class Achievement: from ba._language import Lstr from ba._messages import DieMessage from ba._generated.enums import TimeType, SpecialChar + app = _ba.app app.ach.last_achievement_display_time = _ba.time(TimeType.REAL) @@ -948,8 +1230,8 @@ class Achievement: _ba.playsound(_ba.getsound('achievement'), host_only=True) else: _ba.timer( - 0.5, - lambda: _ba.playsound(_ba.getsound('ding'), host_only=True)) + 0.5, lambda: _ba.playsound(_ba.getsound('ding'), host_only=True) + ) in_time = 0.300 out_time = 3.5 @@ -967,74 +1249,83 @@ class Achievement: # Use a real-timer in the UI context so the removal runs even # if our activity/session dies. with _ba.Context('ui'): - _ba.timer(in_time + out_time, - self._remove_banner_slot, - timetype=TimeType.REAL) + _ba.timer( + in_time + out_time, + self._remove_banner_slot, + timetype=TimeType.REAL, + ) break i += 1 assert self._completion_banner_slot is not None y_offs = 110 * self._completion_banner_slot objs: list[ba.Actor] = [] - obj = Image(_ba.gettexture('shadow'), - position=(-30, 30 + y_offs), - front=True, - attach=Image.Attach.BOTTOM_CENTER, - transition=Image.Transition.IN_BOTTOM, - vr_depth=base_vr_depth - 100, - transition_delay=in_time, - transition_out_delay=out_time, - color=(0.0, 0.1, 0, 1), - scale=(1000, 300)).autoretain() + obj = Image( + _ba.gettexture('shadow'), + position=(-30, 30 + y_offs), + front=True, + attach=Image.Attach.BOTTOM_CENTER, + transition=Image.Transition.IN_BOTTOM, + vr_depth=base_vr_depth - 100, + transition_delay=in_time, + transition_out_delay=out_time, + color=(0.0, 0.1, 0, 1), + scale=(1000, 300), + ).autoretain() objs.append(obj) assert obj.node obj.node.host_only = True - obj = Image(_ba.gettexture('light'), - position=(-180, 60 + y_offs), - front=True, - attach=Image.Attach.BOTTOM_CENTER, - vr_depth=base_vr_depth, - transition=Image.Transition.IN_BOTTOM, - transition_delay=in_time, - transition_out_delay=out_time, - color=(1.8, 1.8, 1.0, 0.0), - scale=(40, 300)).autoretain() + obj = Image( + _ba.gettexture('light'), + position=(-180, 60 + y_offs), + front=True, + attach=Image.Attach.BOTTOM_CENTER, + vr_depth=base_vr_depth, + transition=Image.Transition.IN_BOTTOM, + transition_delay=in_time, + transition_out_delay=out_time, + color=(1.8, 1.8, 1.0, 0.0), + scale=(40, 300), + ).autoretain() objs.append(obj) assert obj.node obj.node.host_only = True obj.node.premultiplied = True combine = _ba.newnode('combine', owner=obj.node, attrs={'size': 2}) _gameutils.animate( - combine, 'input0', { + combine, + 'input0', + { in_time: 0, in_time + 0.4: 30, in_time + 0.5: 40, in_time + 0.6: 30, - in_time + 2.0: 0 - }) + in_time + 2.0: 0, + }, + ) _gameutils.animate( - combine, 'input1', { + combine, + 'input1', + { in_time: 0, in_time + 0.4: 200, in_time + 0.5: 500, in_time + 0.6: 200, - in_time + 2.0: 0 - }) + in_time + 2.0: 0, + }, + ) combine.connectattr('output', obj.node, 'scale') - _gameutils.animate(obj.node, - 'rotate', { - 0: 0.0, - 0.35: 360.0 - }, - loop=True) - obj = Image(self.get_icon_texture(True), - position=(-180, 60 + y_offs), - attach=Image.Attach.BOTTOM_CENTER, - front=True, - vr_depth=base_vr_depth - 10, - transition=Image.Transition.IN_BOTTOM, - transition_delay=in_time, - transition_out_delay=out_time, - scale=(100, 100)).autoretain() + _gameutils.animate(obj.node, 'rotate', {0: 0.0, 0.35: 360.0}, loop=True) + obj = Image( + self.get_icon_texture(True), + position=(-180, 60 + y_offs), + attach=Image.Attach.BOTTOM_CENTER, + front=True, + vr_depth=base_vr_depth - 10, + transition=Image.Transition.IN_BOTTOM, + transition_delay=in_time, + transition_out_delay=out_time, + scale=(100, 100), + ).autoretain() objs.append(obj) assert obj.node obj.node.host_only = True @@ -1047,7 +1338,7 @@ class Achievement: in_time + 0.4: 1.5 * color[0], in_time + 0.5: 6.0 * color[0], in_time + 0.6: 1.5 * color[0], - in_time + 2.0: 1.0 * color[0] + in_time + 2.0: 1.0 * color[0], } _gameutils.animate(combine, 'input0', keys) keys = { @@ -1055,7 +1346,7 @@ class Achievement: in_time + 0.4: 1.5 * color[1], in_time + 0.5: 6.0 * color[1], in_time + 0.6: 1.5 * color[1], - in_time + 2.0: 1.0 * color[1] + in_time + 2.0: 1.0 * color[1], } _gameutils.animate(combine, 'input1', keys) keys = { @@ -1063,21 +1354,23 @@ class Achievement: in_time + 0.4: 1.5 * color[2], in_time + 0.5: 6.0 * color[2], in_time + 0.6: 1.5 * color[2], - in_time + 2.0: 1.0 * color[2] + in_time + 2.0: 1.0 * color[2], } _gameutils.animate(combine, 'input2', keys) combine.connectattr('output', obj.node, 'color') - obj = Image(_ba.gettexture('achievementOutline'), - model_transparent=_ba.getmodel('achievementOutline'), - position=(-180, 60 + y_offs), - front=True, - attach=Image.Attach.BOTTOM_CENTER, - vr_depth=base_vr_depth, - transition=Image.Transition.IN_BOTTOM, - transition_delay=in_time, - transition_out_delay=out_time, - scale=(100, 100)).autoretain() + obj = Image( + _ba.gettexture('achievementOutline'), + model_transparent=_ba.getmodel('achievementOutline'), + position=(-180, 60 + y_offs), + front=True, + attach=Image.Attach.BOTTOM_CENTER, + vr_depth=base_vr_depth, + transition=Image.Transition.IN_BOTTOM, + transition_delay=in_time, + transition_out_delay=out_time, + scale=(100, 100), + ).autoretain() assert obj.node obj.node.host_only = True @@ -1089,7 +1382,7 @@ class Achievement: in_time + 0.4: 1.5 * color[0], in_time + 0.5: 6.0 * color[0], in_time + 0.6: 1.5 * color[0], - in_time + 2.0: 1.0 * color[0] + in_time + 2.0: 1.0 * color[0], } _gameutils.animate(combine, 'input0', keys) keys = { @@ -1097,7 +1390,7 @@ class Achievement: in_time + 0.4: 1.5 * color[1], in_time + 0.5: 6.0 * color[1], in_time + 0.6: 1.5 * color[1], - in_time + 2.0: 1.0 * color[1] + in_time + 2.0: 1.0 * color[1], } _gameutils.animate(combine, 'input1', keys) keys = { @@ -1105,116 +1398,130 @@ class Achievement: in_time + 0.4: 1.5 * color[2], in_time + 0.5: 6.0 * color[2], in_time + 0.6: 1.5 * color[2], - in_time + 2.0: 1.0 * color[2] + in_time + 2.0: 1.0 * color[2], } _gameutils.animate(combine, 'input2', keys) combine.connectattr('output', obj.node, 'color') objs.append(obj) - objt = Text(Lstr(value='${A}:', - subs=[('${A}', Lstr(resource='achievementText'))]), - position=(-120, 91 + y_offs), - front=True, - v_attach=Text.VAttach.BOTTOM, - vr_depth=base_vr_depth - 10, - transition=Text.Transition.IN_BOTTOM, - flatness=0.5, - transition_delay=in_time, - transition_out_delay=out_time, - color=(1, 1, 1, 0.8), - scale=0.65).autoretain() + objt = Text( + Lstr( + value='${A}:', subs=[('${A}', Lstr(resource='achievementText'))] + ), + position=(-120, 91 + y_offs), + front=True, + v_attach=Text.VAttach.BOTTOM, + vr_depth=base_vr_depth - 10, + transition=Text.Transition.IN_BOTTOM, + flatness=0.5, + transition_delay=in_time, + transition_out_delay=out_time, + color=(1, 1, 1, 0.8), + scale=0.65, + ).autoretain() objs.append(objt) assert objt.node objt.node.host_only = True - objt = Text(self.display_name, - position=(-120, 50 + y_offs), - front=True, - v_attach=Text.VAttach.BOTTOM, - transition=Text.Transition.IN_BOTTOM, - vr_depth=base_vr_depth, - flatness=0.5, - transition_delay=in_time, - transition_out_delay=out_time, - flash=True, - color=(1, 0.8, 0, 1.0), - scale=1.5).autoretain() + objt = Text( + self.display_name, + position=(-120, 50 + y_offs), + front=True, + v_attach=Text.VAttach.BOTTOM, + transition=Text.Transition.IN_BOTTOM, + vr_depth=base_vr_depth, + flatness=0.5, + transition_delay=in_time, + transition_out_delay=out_time, + flash=True, + color=(1, 0.8, 0, 1.0), + scale=1.5, + ).autoretain() objs.append(objt) assert objt.node objt.node.host_only = True - objt = Text(_ba.charstr(SpecialChar.TICKET), - position=(-120 - 170 + 5, 75 + y_offs - 20), - front=True, - v_attach=Text.VAttach.BOTTOM, - h_align=Text.HAlign.CENTER, - v_align=Text.VAlign.CENTER, - transition=Text.Transition.IN_BOTTOM, - vr_depth=base_vr_depth, - transition_delay=in_time, - transition_out_delay=out_time, - flash=True, - color=(0.5, 0.5, 0.5, 1), - scale=3.0).autoretain() + objt = Text( + _ba.charstr(SpecialChar.TICKET), + position=(-120 - 170 + 5, 75 + y_offs - 20), + front=True, + v_attach=Text.VAttach.BOTTOM, + h_align=Text.HAlign.CENTER, + v_align=Text.VAlign.CENTER, + transition=Text.Transition.IN_BOTTOM, + vr_depth=base_vr_depth, + transition_delay=in_time, + transition_out_delay=out_time, + flash=True, + color=(0.5, 0.5, 0.5, 1), + scale=3.0, + ).autoretain() objs.append(objt) assert objt.node objt.node.host_only = True - objt = Text('+' + str(self.get_award_ticket_value()), - position=(-120 - 180 + 5, 80 + y_offs - 20), - v_attach=Text.VAttach.BOTTOM, - front=True, - h_align=Text.HAlign.CENTER, - v_align=Text.VAlign.CENTER, - transition=Text.Transition.IN_BOTTOM, - vr_depth=base_vr_depth, - flatness=0.5, - shadow=1.0, - transition_delay=in_time, - transition_out_delay=out_time, - flash=True, - color=(0, 1, 0, 1), - scale=1.5).autoretain() + objt = Text( + '+' + str(self.get_award_ticket_value()), + position=(-120 - 180 + 5, 80 + y_offs - 20), + v_attach=Text.VAttach.BOTTOM, + front=True, + h_align=Text.HAlign.CENTER, + v_align=Text.VAlign.CENTER, + transition=Text.Transition.IN_BOTTOM, + vr_depth=base_vr_depth, + flatness=0.5, + shadow=1.0, + transition_delay=in_time, + transition_out_delay=out_time, + flash=True, + color=(0, 1, 0, 1), + scale=1.5, + ).autoretain() objs.append(objt) assert objt.node objt.node.host_only = True # Add the 'x 2' if we've got pro. if app.accounts_v1.have_pro(): - objt = Text('x 2', - position=(-120 - 180 + 45, 80 + y_offs - 50), - v_attach=Text.VAttach.BOTTOM, - front=True, - h_align=Text.HAlign.CENTER, - v_align=Text.VAlign.CENTER, - transition=Text.Transition.IN_BOTTOM, - vr_depth=base_vr_depth, - flatness=0.5, - shadow=1.0, - transition_delay=in_time, - transition_out_delay=out_time, - flash=True, - color=(0.4, 0, 1, 1), - scale=0.9).autoretain() + objt = Text( + 'x 2', + position=(-120 - 180 + 45, 80 + y_offs - 50), + v_attach=Text.VAttach.BOTTOM, + front=True, + h_align=Text.HAlign.CENTER, + v_align=Text.VAlign.CENTER, + transition=Text.Transition.IN_BOTTOM, + vr_depth=base_vr_depth, + flatness=0.5, + shadow=1.0, + transition_delay=in_time, + transition_out_delay=out_time, + flash=True, + color=(0.4, 0, 1, 1), + scale=0.9, + ).autoretain() objs.append(objt) assert objt.node objt.node.host_only = True - objt = Text(self.description_complete, - position=(-120, 30 + y_offs), - front=True, - v_attach=Text.VAttach.BOTTOM, - transition=Text.Transition.IN_BOTTOM, - vr_depth=base_vr_depth - 10, - flatness=0.5, - transition_delay=in_time, - transition_out_delay=out_time, - color=(1.0, 0.7, 0.5, 1.0), - scale=0.8).autoretain() + objt = Text( + self.description_complete, + position=(-120, 30 + y_offs), + front=True, + v_attach=Text.VAttach.BOTTOM, + transition=Text.Transition.IN_BOTTOM, + vr_depth=base_vr_depth - 10, + flatness=0.5, + transition_delay=in_time, + transition_out_delay=out_time, + color=(1.0, 0.7, 0.5, 1.0), + scale=0.8, + ).autoretain() objs.append(objt) assert objt.node objt.node.host_only = True for actor in objs: - _ba.timer(out_time + 1.000, - WeakCall(actor.handlemessage, DieMessage())) + _ba.timer( + out_time + 1.000, WeakCall(actor.handlemessage, DieMessage()) + ) diff --git a/dist/ba_data/python/ba/_activity.py b/dist/ba_data/python/ba/_activity.py index 68d740d..ff0acb5 100644 --- a/dist/ba_data/python/ba/_activity.py +++ b/dist/ba_data/python/ba/_activity.py @@ -9,8 +9,12 @@ from typing import TYPE_CHECKING, Generic, TypeVar import _ba from ba._team import Team from ba._player import Player -from ba._error import (print_exception, SessionTeamNotFoundError, - SessionPlayerNotFoundError, NodeNotFoundError) +from ba._error import ( + print_exception, + SessionTeamNotFoundError, + SessionPlayerNotFoundError, + NodeNotFoundError, +) from ba._dependency import DependencyComponent from ba._general import Call, verify_object_death from ba._messages import UNHANDLED @@ -199,8 +203,11 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): session = self._session() if session is not None: _ba.pushcall( - Call(session.transitioning_out_activity_was_freed, - self.can_show_ad_on_death)) + Call( + session.transitioning_out_activity_was_freed, + self.can_show_ad_on_death, + ) + ) @property def globalsnode(self) -> ba.Node: @@ -220,6 +227,7 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): """ if self._stats is None: from ba._error import NotFoundError + raise NotFoundError() return self._stats @@ -285,7 +293,8 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): 5.0, Call(self._check_activity_death, ref, [0]), repeat=True, - timetype=TimeType.REAL) + timetype=TimeType.REAL, + ) # Run _expire in an empty context; nothing should be happening in # there except deleting things which requires no context. @@ -296,8 +305,9 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): with _ba.Context('empty'): self._expire() else: - raise RuntimeError(f'destroy() called when' - f' already expired for {self}') + raise RuntimeError( + f'destroy() called when' f' already expired for {self}' + ) def retain_actor(self, actor: ba.Actor) -> None: """Add a strong-reference to a ba.Actor to this Activity. @@ -308,6 +318,7 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): """ if __debug__: from ba._actor import Actor + assert isinstance(actor, Actor) self._actor_refs.append(actor) @@ -318,6 +329,7 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): """ if __debug__: from ba._actor import Actor + assert isinstance(actor, Actor) self._actor_weak_refs.append(weakref.ref(actor)) @@ -330,6 +342,7 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): session = self._session() if session is None: from ba._error import SessionNotFoundError + raise SessionNotFoundError() return session @@ -381,7 +394,7 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): def has_transitioned_in(self) -> bool: """Return whether ba.Activity.on_transition_in() - has been called.""" + has been called.""" return self._has_transitioned_in def has_begun(self) -> bool: @@ -425,7 +438,8 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): if self.inherits_vr_overlay_center and prev_globals is not None: glb.vr_overlay_center = prev_globals.vr_overlay_center glb.vr_overlay_center_enabled = ( - prev_globals.vr_overlay_center_enabled) + prev_globals.vr_overlay_center_enabled + ) # If they want to inherit tint from the previous self. if self.inherits_tint and prev_globals is not None: @@ -435,9 +449,9 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): # Start pruning our various things periodically. self._prune_dead_actors() - self._prune_dead_actors_timer = _ba.Timer(5.17, - self._prune_dead_actors, - repeat=True) + self._prune_dead_actors_timer = _ba.Timer( + 5.17, self._prune_dead_actors, repeat=True + ) _ba.timer(13.3, self._prune_delay_deletes, repeat=True) @@ -491,10 +505,9 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): # activity launch; just wanna be sure that is intentional. self.on_begin() - def end(self, - results: Any = None, - delay: float = 0.0, - force: bool = False) -> None: + def end( + self, results: Any = None, delay: float = 0.0, force: bool = False + ) -> None: """Commences Activity shutdown and delivers results to the ba.Session. 'delay' is the time delay before the Activity actually ends @@ -543,7 +556,8 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): sessionplayer.setactivity(self) with _ba.Context(self): sessionplayer.activityplayer = player = self.create_player( - sessionplayer) + sessionplayer + ) player.postinit(sessionplayer) assert player not in team.players @@ -654,7 +668,8 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): self._teams_that_left.append(weakref.ref(team)) def _reset_session_player_for_no_activity( - self, sessionplayer: ba.SessionPlayer) -> None: + self, sessionplayer: ba.SessionPlayer + ) -> None: # Let's be extra-defensive here: killing a node/input-call/etc # could trigger user-code resulting in errors, but we would still @@ -664,13 +679,15 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): except Exception: print_exception( f'Error resetting SessionPlayer node on {sessionplayer}' - f' for {self}.') + f' for {self}.' + ) try: sessionplayer.resetinput() except Exception: print_exception( f'Error resetting SessionPlayer input on {sessionplayer}' - f' for {self}.') + f' for {self}.' + ) # These should never fail I think... sessionplayer.setactivity(None) @@ -691,21 +708,26 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): self._playertype = type(self).__orig_bases__[-1].__args__[0] if not isinstance(self._playertype, type): self._playertype = Player - print(f'ERROR: {type(self)} was not passed a Player' - f' type argument; please explicitly pass ba.Player' - f' if you do not want to override it.') + print( + f'ERROR: {type(self)} was not passed a Player' + f' type argument; please explicitly pass ba.Player' + f' if you do not want to override it.' + ) self._teamtype = type(self).__orig_bases__[-1].__args__[1] if not isinstance(self._teamtype, type): self._teamtype = Team - print(f'ERROR: {type(self)} was not passed a Team' - f' type argument; please explicitly pass ba.Team' - f' if you do not want to override it.') + print( + f'ERROR: {type(self)} was not passed a Team' + f' type argument; please explicitly pass ba.Team' + f' if you do not want to override it.' + ) assert issubclass(self._playertype, Player) assert issubclass(self._teamtype, Team) @classmethod - def _check_activity_death(cls, activity_ref: weakref.ref[Activity], - counter: list[int]) -> None: + def _check_activity_death( + cls, activity_ref: weakref.ref[Activity], counter: list[int] + ) -> None: """Sanity check to make sure an Activity was destroyed properly. Receives a weakref to a ba.Activity which should have torn itself @@ -713,27 +735,20 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): and/or print debugging info if the Activity still exists. """ try: - import gc - import types activity = activity_ref() - print('ERROR: Activity is not dying when expected:', activity, - '(warning ' + str(counter[0] + 1) + ')') - print('This means something is still strong-referencing it.') + print( + 'ERROR: Activity is not dying when expected:', + activity, + '(warning ' + str(counter[0] + 1) + ')', + ) + print( + 'This means something is still strong-referencing it.\n' + 'Check out methods such as efro.debug.printrefs() to' + ' help debug this sort of thing.' + ) + # Note: no longer calling gc.get_referrers() here because it's + # usage can bork stuff. (see notes at top of efro.debug) counter[0] += 1 - - # FIXME: Running the code below shows us references but winds up - # keeping the object alive; need to figure out why. - # For now we just print refs if the count gets to 3, and then we - # kill the app at 4 so it doesn't matter anyway. - if counter[0] == 3: - print('Activity references for', activity, ':') - refs = list(gc.get_referrers(activity)) - i = 1 - for ref in refs: - if isinstance(ref, types.FrameType): - continue - print(' reference', i, ':', ref) - i += 1 if counter[0] == 4: print('Killing app due to stuck activity... :-(') _ba.quit() @@ -784,8 +799,9 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): try: actor.on_expire() except Exception: - print_exception(f'Error in Actor.on_expire()' - f' for {actor_ref()}.') + print_exception( + f'Error in Actor.on_expire()' f' for {actor_ref()}.' + ) def _expire_players(self) -> None: diff --git a/dist/ba_data/python/ba/_activitytypes.py b/dist/ba_data/python/ba/_activitytypes.py index 81e7a9f..ebdd06b 100644 --- a/dist/ba_data/python/ba/_activitytypes.py +++ b/dist/ba_data/python/ba/_activitytypes.py @@ -9,6 +9,7 @@ import _ba from ba._activity import Activity from ba._music import setmusic, MusicType from ba._generated.enums import InputType, UIScale + # False-positive from pylint due to our class-generics-filter. from ba._player import EmptyPlayer # pylint: disable=W0611 from ba._team import EmptyTeam # pylint: disable=W0611 @@ -40,6 +41,7 @@ class EndSessionActivity(Activity[EmptyPlayer, EmptyTeam]): # pylint: disable=cyclic-import from bastd.mainmenu import MainMenuSession from ba._general import Call + super().on_begin() _ba.unlock_all_input() _ba.app.ads.call_after_ad(Call(_ba.new_host_session, MainMenuSession)) @@ -72,10 +74,11 @@ class JoinActivity(Activity[EmptyPlayer, EmptyTeam]): # pylint: disable=cyclic-import from bastd.actor.tipstext import TipsText from bastd.actor.background import Background + super().on_transition_in() - self._background = Background(fade_time=0.5, - start_faded=True, - show_logo=True) + self._background = Background( + fade_time=0.5, start_faded=True, show_logo=True + ) self._tips_text = TipsText() setmusic(MusicType.CHAR_SELECT) self._join_info = self.session.lobby.create_join_info() @@ -103,10 +106,11 @@ class TransitionActivity(Activity[EmptyPlayer, EmptyTeam]): def on_transition_in(self) -> None: # pylint: disable=cyclic-import from bastd.actor import background # FIXME: Don't use bastd from ba. + super().on_transition_in() - self._background = background.Background(fade_time=0.5, - start_faded=False, - show_logo=False) + self._background = background.Background( + fade_time=0.5, start_faded=False, show_logo=False + ) def on_begin(self) -> None: super().on_begin() @@ -143,9 +147,11 @@ class ScoreScreenActivity(Activity[EmptyPlayer, EmptyTeam]): def on_player_join(self, player: EmptyPlayer) -> None: from ba._general import WeakCall + super().on_player_join(player) time_till_assign = max( - 0, self._birth_time + self._min_view_time - _ba.time()) + 0, self._birth_time + self._min_view_time - _ba.time() + ) # If we're still kicking at the end of our assign-delay, assign this # guy's input to trigger us. @@ -154,10 +160,11 @@ class ScoreScreenActivity(Activity[EmptyPlayer, EmptyTeam]): def on_transition_in(self) -> None: from bastd.actor.tipstext import TipsText from bastd.actor.background import Background + super().on_transition_in() - self._background = Background(fade_time=0.5, - start_faded=False, - show_logo=True) + self._background = Background( + fade_time=0.5, start_faded=False, show_logo=True + ) if self._default_show_tips: self._tips_text = TipsText() setmusic(self.default_music) @@ -166,6 +173,7 @@ class ScoreScreenActivity(Activity[EmptyPlayer, EmptyTeam]): # pylint: disable=cyclic-import from bastd.actor.text import Text from ba import _language + super().on_begin() import custom_hooks custom_hooks.score_screen_on_begin(self._stats) @@ -180,24 +188,30 @@ class ScoreScreenActivity(Activity[EmptyPlayer, EmptyTeam]): else: sval = _language.Lstr(resource='pressAnyButtonText') - Text(self._custom_continue_message - if self._custom_continue_message is not None else sval, - v_attach=Text.VAttach.BOTTOM, - h_align=Text.HAlign.CENTER, - flash=True, - vr_depth=50, - position=(0, 10), - scale=0.8, - color=(0.5, 0.7, 0.5, 0.5), - transition=Text.Transition.IN_BOTTOM_SLOW, - transition_delay=self._min_view_time).autoretain() + Text( + self._custom_continue_message + if self._custom_continue_message is not None + else sval, + v_attach=Text.VAttach.BOTTOM, + h_align=Text.HAlign.CENTER, + flash=True, + vr_depth=50, + position=(0, 10), + scale=0.8, + color=(0.5, 0.7, 0.5, 0.5), + transition=Text.Transition.IN_BOTTOM_SLOW, + transition_delay=self._min_view_time, + ).autoretain() def _player_press(self) -> None: # If this activity is a good 'end point', ask server-mode just once if # it wants to do anything special like switch sessions or kill the app. - if (self._allow_server_transition and _ba.app.server is not None - and self._server_transitioning is None): + if ( + self._allow_server_transition + and _ba.app.server is not None + and self._server_transitioning is None + ): self._server_transitioning = _ba.app.server.handle_transition() assert isinstance(self._server_transitioning, bool) @@ -213,6 +227,12 @@ class ScoreScreenActivity(Activity[EmptyPlayer, EmptyTeam]): # Just to be extra careful, don't assign if we're transitioning out. # (though theoretically that should be ok). if not self.is_transitioning_out() and player: - player.assigninput((InputType.JUMP_PRESS, InputType.PUNCH_PRESS, - InputType.BOMB_PRESS, InputType.PICK_UP_PRESS), - self._player_press) + player.assigninput( + ( + InputType.JUMP_PRESS, + InputType.PUNCH_PRESS, + InputType.BOMB_PRESS, + InputType.PICK_UP_PRESS, + ), + self._player_press, + ) diff --git a/dist/ba_data/python/ba/_ads.py b/dist/ba_data/python/ba/_ads.py index 6cf7730..243b29d 100644 --- a/dist/ba_data/python/ba/_ads.py +++ b/dist/ba_data/python/ba/_ads.py @@ -38,31 +38,41 @@ class AdsSubsystem: # Print this message once every 10 minutes at most. tval = _ba.time(TimeType.REAL) - if (self.last_in_game_ad_remove_message_show_time is None or - (tval - self.last_in_game_ad_remove_message_show_time > 60 * 10)): + if self.last_in_game_ad_remove_message_show_time is None or ( + tval - self.last_in_game_ad_remove_message_show_time > 60 * 10 + ): self.last_in_game_ad_remove_message_show_time = tval with _ba.Context('ui'): _ba.timer( 1.0, - lambda: _ba.screenmessage(Lstr( - resource='removeInGameAdsText', - subs=[('${PRO}', - Lstr(resource='store.bombSquadProNameText')), - ('${APP_NAME}', Lstr(resource='titleText'))]), - color=(1, 1, 0)), - timetype=TimeType.REAL) + lambda: _ba.screenmessage( + Lstr( + resource='removeInGameAdsText', + subs=[ + ( + '${PRO}', + Lstr(resource='store.bombSquadProNameText'), + ), + ('${APP_NAME}', Lstr(resource='titleText')), + ], + ), + color=(1, 1, 0), + ), + timetype=TimeType.REAL, + ) - def show_ad(self, - purpose: str, - on_completion_call: Callable[[], Any] | None = None) -> None: + def show_ad( + self, purpose: str, on_completion_call: Callable[[], Any] | None = None + ) -> None: """(internal)""" self.last_ad_purpose = purpose _ba.show_ad(purpose, on_completion_call) def show_ad_2( - self, - purpose: str, - on_completion_call: Callable[[bool], Any] | None = None) -> None: + self, + purpose: str, + on_completion_call: Callable[[bool], Any] | None = None, + ) -> None: """(internal)""" self.last_ad_purpose = purpose _ba.show_ad_2(purpose, on_completion_call) @@ -73,6 +83,7 @@ class AdsSubsystem: # pylint: disable=too-many-branches # pylint: disable=too-many-locals from ba._generated.enums import TimeType + app = _ba.app show = True @@ -95,16 +106,22 @@ class AdsSubsystem: launch_count = app.config.get('launchCount', 0) # If we're seeing short ads we may want to space them differently. - interval_mult = (_internal.get_v1_account_misc_read_val( - 'ads.shortIntervalMult', 1.0) - if self.last_ad_was_short else 1.0) + interval_mult = ( + _internal.get_v1_account_misc_read_val( + 'ads.shortIntervalMult', 1.0 + ) + if self.last_ad_was_short + else 1.0 + ) if self.ad_amt is None: if launch_count <= 1: self.ad_amt = _internal.get_v1_account_misc_read_val( - 'ads.startVal1', 0.99) + 'ads.startVal1', 0.99 + ) else: self.ad_amt = _internal.get_v1_account_misc_read_val( - 'ads.startVal2', 1.0) + 'ads.startVal2', 1.0 + ) interval = None else: # So far we're cleared to show; now calc our @@ -113,27 +130,33 @@ class AdsSubsystem: # playing). base = 'ads' if _ba.has_video_ads() else 'ads2' min_lc = _internal.get_v1_account_misc_read_val( - base + '.minLC', 0.0) + base + '.minLC', 0.0 + ) max_lc = _internal.get_v1_account_misc_read_val( - base + '.maxLC', 5.0) - min_lc_scale = (_internal.get_v1_account_misc_read_val( - base + '.minLCScale', 0.25)) - max_lc_scale = (_internal.get_v1_account_misc_read_val( - base + '.maxLCScale', 0.34)) - min_lc_interval = (_internal.get_v1_account_misc_read_val( - base + '.minLCInterval', 360)) - max_lc_interval = (_internal.get_v1_account_misc_read_val( - base + '.maxLCInterval', 300)) + base + '.maxLC', 5.0 + ) + min_lc_scale = _internal.get_v1_account_misc_read_val( + base + '.minLCScale', 0.25 + ) + max_lc_scale = _internal.get_v1_account_misc_read_val( + base + '.maxLCScale', 0.34 + ) + min_lc_interval = _internal.get_v1_account_misc_read_val( + base + '.minLCInterval', 360 + ) + max_lc_interval = _internal.get_v1_account_misc_read_val( + base + '.maxLCInterval', 300 + ) if launch_count < min_lc: lc_amt = 0.0 elif launch_count > max_lc: lc_amt = 1.0 else: - lc_amt = ((float(launch_count) - min_lc) / - (max_lc - min_lc)) + lc_amt = (float(launch_count) - min_lc) / (max_lc - min_lc) incr = (1.0 - lc_amt) * min_lc_scale + lc_amt * max_lc_scale - interval = ((1.0 - lc_amt) * min_lc_interval + - lc_amt * max_lc_interval) + interval = ( + 1.0 - lc_amt + ) * min_lc_interval + lc_amt * max_lc_interval self.ad_amt += incr assert self.ad_amt is not None if self.ad_amt >= 1.0: @@ -143,12 +166,14 @@ class AdsSubsystem: # After we've reached the traditional show-threshold once, # try again whenever its been INTERVAL since our last successful # show. - elif ( - self.attempted_first_ad and - (self.last_ad_completion_time is None or - (interval is not None - and _ba.time(TimeType.REAL) - self.last_ad_completion_time > - (interval * interval_mult)))): + elif self.attempted_first_ad and ( + self.last_ad_completion_time is None + or ( + interval is not None + and _ba.time(TimeType.REAL) - self.last_ad_completion_time + > (interval * interval_mult) + ) + ): # Reset our other counter too in this case. self.ad_amt = 0.0 else: @@ -161,7 +186,6 @@ class AdsSubsystem: # (in case some random ad network doesn't properly deliver its # completion callback). class _Payload: - def __init__(self, pcall: Callable[[], Any]): self._call = pcall self._ran = False @@ -171,20 +195,31 @@ class AdsSubsystem: if not self._ran: if fallback: print( - ('ERROR: relying on fallback ad-callback! ' - 'last network: ' + app.ads.last_ad_network + - ' (set ' + str( - int(time.time() - - app.ads.last_ad_network_set_time)) + - 's ago); purpose=' + app.ads.last_ad_purpose)) + ( + 'ERROR: relying on fallback ad-callback! ' + 'last network: ' + + app.ads.last_ad_network + + ' (set ' + + str( + int( + time.time() + - app.ads.last_ad_network_set_time + ) + ) + + 's ago); purpose=' + + app.ads.last_ad_purpose + ) + ) _ba.pushcall(self._call) self._ran = True payload = _Payload(call) with _ba.Context('ui'): - _ba.timer(5.0, - lambda: payload.run(fallback=True), - timetype=TimeType.REAL) + _ba.timer( + 5.0, + lambda: payload.run(fallback=True), + timetype=TimeType.REAL, + ) self.show_ad('between_game', on_completion_call=payload.run) else: _ba.pushcall(call) # Just run the callback without the ad. diff --git a/dist/ba_data/python/ba/_analytics.py b/dist/ba_data/python/ba/_analytics.py index 27c8996..a3c25e8 100644 --- a/dist/ba_data/python/ba/_analytics.py +++ b/dist/ba_data/python/ba/_analytics.py @@ -20,6 +20,7 @@ def game_begin_analytics() -> None: from ba._freeforallsession import FreeForAllSession from ba._coopsession import CoopSession from ba._gameactivity import GameActivity + activity = _ba.getactivity(False) session = _ba.getsession(False) @@ -31,8 +32,11 @@ def game_begin_analytics() -> None: campaign = session.campaign assert campaign is not None _ba.set_analytics_screen( - 'Coop Game: ' + campaign.name + ' ' + - campaign.getlevel(_ba.app.coop_session_args['level']).name) + 'Coop Game: ' + + campaign.name + + ' ' + + campaign.getlevel(_ba.app.coop_session_args['level']).name + ) _ba.increment_analytics_count('Co-op round start') if len(activity.players) == 1: _ba.increment_analytics_count('Co-op round start 1 human player') @@ -49,9 +53,11 @@ def game_begin_analytics() -> None: if len(activity.players) == 1: _ba.increment_analytics_count('Teams round start 1 human player') elif 1 < len(activity.players) < 8: - _ba.increment_analytics_count('Teams round start ' + - str(len(activity.players)) + - ' human players') + _ba.increment_analytics_count( + 'Teams round start ' + + str(len(activity.players)) + + ' human players' + ) elif len(activity.players) >= 8: _ba.increment_analytics_count('Teams round start 8+ human players') @@ -60,14 +66,18 @@ def game_begin_analytics() -> None: _ba.increment_analytics_count('Free-for-all round start') if len(activity.players) == 1: _ba.increment_analytics_count( - 'Free-for-all round start 1 human player') + 'Free-for-all round start 1 human player' + ) elif 1 < len(activity.players) < 8: - _ba.increment_analytics_count('Free-for-all round start ' + - str(len(activity.players)) + - ' human players') + _ba.increment_analytics_count( + 'Free-for-all round start ' + + str(len(activity.players)) + + ' human players' + ) elif len(activity.players) >= 8: _ba.increment_analytics_count( - 'Free-for-all round start 8+ human players') + 'Free-for-all round start 8+ human players' + ) # For some analytics tracking on the c layer. _ba.reset_game_activity_tracking() diff --git a/dist/ba_data/python/ba/_app.py b/dist/ba_data/python/ba/_app.py index b976517..46ac8e6 100644 --- a/dist/ba_data/python/ba/_app.py +++ b/dist/ba_data/python/ba/_app.py @@ -198,6 +198,7 @@ class App: accordingly and set to target the new API version number. """ from ba._meta import CURRENT_API_VERSION + return CURRENT_API_VERSION @property @@ -369,19 +370,33 @@ class App: # FIXME: This should not be hard-coded. for maptype in [ - stdmaps.HockeyStadium, stdmaps.FootballStadium, - stdmaps.Bridgit, stdmaps.BigG, stdmaps.Roundabout, - stdmaps.MonkeyFace, stdmaps.ZigZag, stdmaps.ThePad, - stdmaps.DoomShroom, stdmaps.LakeFrigid, stdmaps.TipTop, - stdmaps.CragCastle, stdmaps.TowerD, stdmaps.HappyThoughts, - stdmaps.StepRightUp, stdmaps.Courtyard, stdmaps.Rampage + stdmaps.HockeyStadium, + stdmaps.FootballStadium, + stdmaps.Bridgit, + stdmaps.BigG, + stdmaps.Roundabout, + stdmaps.MonkeyFace, + stdmaps.ZigZag, + stdmaps.ThePad, + stdmaps.DoomShroom, + stdmaps.LakeFrigid, + stdmaps.TipTop, + stdmaps.CragCastle, + stdmaps.TowerD, + stdmaps.HappyThoughts, + stdmaps.StepRightUp, + stdmaps.Courtyard, + stdmaps.Rampage, ]: _map.register_map(maptype) # Non-test, non-debug builds should generally be blessed; warn if not. # (so I don't accidentally release a build that can't play tourneys) - if (not self.debug_build and not self.test_build - and not _internal.is_blessed()): + if ( + not self.debug_build + and not self.test_build + and not _internal.is_blessed() + ): _ba.screenmessage('WARNING: NON-BLESSED BUILD', color=(1, 0, 0)) # If there's a leftover log file, attempt to upload it to the @@ -393,6 +408,7 @@ class App: if not self.config_file_healthy: if self.platform in ('mac', 'linux', 'windows'): from bastd.ui import configerror + configerror.ConfigErrorWindow() return @@ -418,10 +434,13 @@ class App: # pending special offer. def check_special_offer() -> None: from bastd.ui.specialoffer import show_offer + config = self.config - if ('pendingSpecialOffer' in config - and _internal.get_public_login_id() - == config['pendingSpecialOffer']['a']): + if ( + 'pendingSpecialOffer' in config + and _internal.get_public_login_id() + == config['pendingSpecialOffer']['a'] + ): self.special_offer = config['pendingSpecialOffer']['o'] show_offer() @@ -436,8 +455,9 @@ class App: # See note below in on_app_pause. if self.state != self.State.LAUNCHING: - logging.error('on_app_launch found state %s; expected LAUNCHING.', - self.state) + logging.error( + 'on_app_launch found state %s; expected LAUNCHING.', self.state + ) self._launch_completed = True self._update_state() @@ -501,6 +521,7 @@ class App: def read_config(self) -> None: """(internal)""" from ba._appconfig import read_config + self._config, self.config_file_healthy = read_config() def pause(self) -> None: @@ -510,8 +531,11 @@ class App: to pause ..we now no longer pause if there are connected clients. """ activity: ba.Activity | None = _ba.get_foreground_host_activity() - if (activity is not None and activity.allow_pausing - and not _ba.have_connected_clients()): + if ( + activity is not None + and activity.allow_pausing + and not _ba.have_connected_clients() + ): from ba._language import Lstr from ba._nodeactor import NodeActor @@ -525,13 +549,16 @@ class App: # FIXME: This should not be an attr on Actor. activity.paused_text = NodeActor( - _ba.newnode('text', - attrs={ - 'text': Lstr(resource='pausedByHostText'), - 'client_only': True, - 'flatness': 1.0, - 'h_align': 'center' - })) + _ba.newnode( + 'text', + attrs={ + 'text': Lstr(resource='pausedByHostText'), + 'client_only': True, + 'flatness': 1.0, + 'h_align': 'center', + }, + ) + ) def resume(self) -> None: """Resume the game due to a user request or menu closing. @@ -562,13 +589,15 @@ class App: # Make note to add it to our challenges UI. self.custom_coop_practice_games.append(f'Challenges:{level.name}') - def return_to_main_menu_session_gracefully(self, - reset_ui: bool = True) -> None: + def return_to_main_menu_session_gracefully( + self, reset_ui: bool = True + ) -> None: """Attempt to cleanly get back to the main menu.""" # pylint: disable=cyclic-import from ba import _benchmark from ba._general import Call from bastd.mainmenu import MainMenuSession + if reset_ui: _ba.app.ui.clear_main_menu_window() @@ -587,10 +616,9 @@ class App: # Kick off a little transaction so we'll hopefully have all the # latest account state when we get back to the menu. - _internal.add_transaction({ - 'type': 'END_SESSION', - 'sType': str(type(host_session)) - }) + _internal.add_transaction( + {'type': 'END_SESSION', 'sType': str(type(host_session))} + ) _internal.run_transactions() host_session.end() @@ -609,14 +637,14 @@ class App: else: self.main_menu_resume_callbacks.append(call) - def launch_coop_game(self, - game: str, - force: bool = False, - args: dict | None = None) -> bool: + def launch_coop_game( + self, game: str, force: bool = False, args: dict | None = None + ) -> bool: """High level way to launch a local co-op session.""" # pylint: disable=cyclic-import from ba._campaign import getcampaign from bastd.ui.coop.level import CoopLevelLockedWindow + if args is None: args = {} if game == '': @@ -633,7 +661,8 @@ class App: if not level.complete: CoopLevelLockedWindow( campaign.getlevel(levelname).displayname, - campaign.getlevel(level.name).displayname) + campaign.getlevel(level.name).displayname, + ) return False # Ok, we're good to go. @@ -646,12 +675,15 @@ class App: def _fade_end() -> None: from ba import _coopsession + try: _ba.new_host_session(_coopsession.CoopSession) except Exception: from ba import _error + _error.print_exception() from bastd.mainmenu import MainMenuSession + _ba.new_host_session(MainMenuSession) _ba.fade_screen(False, endcall=_fade_end) @@ -660,6 +692,7 @@ class App: def handle_deep_link(self, url: str) -> None: """Handle a deep link URL.""" from ba._language import Lstr + appname = _ba.appname() if url.startswith(f'{appname}://code/'): code = url.replace(f'{appname}://code/', '') diff --git a/dist/ba_data/python/ba/_appconfig.py b/dist/ba_data/python/ba/_appconfig.py index 4fd28de..b433971 100644 --- a/dist/ba_data/python/ba/_appconfig.py +++ b/dist/ba_data/python/ba/_appconfig.py @@ -115,16 +115,29 @@ def read_config() -> tuple[AppConfig, bool]: config_file_healthy = True except Exception as exc: - print(('error reading config file at time ' + - str(_ba.time(TimeType.REAL)) + ': \'' + config_file_path + - '\':\n'), exc) + print( + ( + 'error reading config file at time ' + + str(_ba.time(TimeType.REAL)) + + ': \'' + + config_file_path + + '\':\n' + ), + exc, + ) # Whenever this happens lets back up the broken one just in case it # gets overwritten accidentally. - print(('backing up current config file to \'' + config_file_path + - ".broken\'")) + print( + ( + 'backing up current config file to \'' + + config_file_path + + ".broken\'" + ) + ) try: import shutil + shutil.copyfile(config_file_path, config_file_path + '.broken') except Exception as exc2: print('EXC copying broken config:', exc2) @@ -154,8 +167,11 @@ def commit_app_config(force: bool = False) -> None: (internal) """ from ba._internal import mark_config_dirty + if not _ba.app.config_file_healthy and not force: - print('Current config file is broken; ' - 'skipping write to avoid losing settings.') + print( + 'Current config file is broken; ' + 'skipping write to avoid losing settings.' + ) return mark_config_dirty() diff --git a/dist/ba_data/python/ba/_appdelegate.py b/dist/ba_data/python/ba/_appdelegate.py index 9f967ae..b21ec10 100644 --- a/dist/ba_data/python/ba/_appdelegate.py +++ b/dist/ba_data/python/ba/_appdelegate.py @@ -17,9 +17,12 @@ class AppDelegate: """ def create_default_game_settings_ui( - self, gameclass: type[ba.GameActivity], - sessiontype: type[ba.Session], settings: dict | None, - completion_call: Callable[[dict | None], None]) -> None: + self, + gameclass: type[ba.GameActivity], + sessiontype: type[ba.Session], + settings: dict | None, + completion_call: Callable[[dict | None], None], + ) -> None: """Launch a UI to configure the given game config. It should manipulate the contents of config and call completion_call @@ -27,5 +30,7 @@ class AppDelegate: """ del gameclass, sessiontype, settings, completion_call # Unused. from ba import _error + _error.print_error( - "create_default_game_settings_ui needs to be overridden") + "create_default_game_settings_ui needs to be overridden" + ) diff --git a/dist/ba_data/python/ba/_apputils.py b/dist/ba_data/python/ba/_apputils.py index d7c2cfe..166b4d3 100644 --- a/dist/ba_data/python/ba/_apputils.py +++ b/dist/ba_data/python/ba/_apputils.py @@ -42,6 +42,7 @@ def is_browser_likely_available() -> bool: def get_remote_app_name() -> ba.Lstr: """(internal)""" from ba import _language + return _language.Lstr(resource='remote_app.app_name') @@ -59,6 +60,7 @@ def handle_v1_cloud_log() -> None: from ba._net import master_server_post from ba._generated.enums import TimeType from ba._internal import get_news_show + app = _ba.app app.log_have_new = True if not app.log_upload_timer_started: @@ -112,10 +114,12 @@ def handle_v1_cloud_log() -> None: if not _ba.is_log_full(): with _ba.Context('ui'): - _ba.timer(600.0, - _reset, - timetype=TimeType.REAL, - suppress_format_warning=True) + _ba.timer( + 600.0, + _reset, + timetype=TimeType.REAL, + suppress_format_warning=True, + ) def handle_leftover_v1_cloud_log_file() -> None: @@ -125,8 +129,9 @@ def handle_leftover_v1_cloud_log_file() -> None: from ba._net import master_server_post if os.path.exists(_ba.get_v1_cloud_log_file_path()): - with open(_ba.get_v1_cloud_log_file_path(), - encoding='utf-8') as infile: + with open( + _ba.get_v1_cloud_log_file_path(), encoding='utf-8' + ) as infile: info = json.loads(infile.read()) infile.close() do_send = should_submit_debug_info() @@ -150,6 +155,7 @@ def handle_leftover_v1_cloud_log_file() -> None: os.remove(_ba.get_v1_cloud_log_file_path()) except Exception: from ba import _error + _error.print_exception('Error handling leftover log file.') @@ -164,7 +170,13 @@ def garbage_collect_session_end() -> None: print('PYTHON GC FOUND', len(gc.garbage), 'UNCOLLECTIBLE OBJECTS:') for i, obj in enumerate(gc.garbage): print(str(i) + ':', obj) - print_live_object_warnings('after session shutdown') + + # NOTE: no longer running these checks. Perhaps we can allow + # running them with an explicit flag passed, but we should never + # run them by default because gc.get_objects() can mess up the app. + # See notes at top of efro.debug. + if bool(False): + print_live_object_warnings('after session shutdown') def garbage_collect() -> None: @@ -180,10 +192,15 @@ def garbage_collect() -> None: def print_live_object_warnings( - when: Any, - ignore_session: ba.Session | None = None, - ignore_activity: ba.Activity | None = None) -> None: - """Print warnings for remaining objects in the current context.""" + when: Any, + ignore_session: ba.Session | None = None, + ignore_activity: ba.Activity | None = None, +) -> None: + """Print warnings for remaining objects in the current context. + + IMPORTANT - don't call this in production; usage of gc.get_objects() + can bork Python. See notes at top of efro.debug module. + """ # pylint: disable=cyclic-import from ba._session import Session from ba._actor import Actor @@ -229,13 +246,17 @@ def print_corrupt_file_error() -> None: """Print an error if a corrupt file is found.""" from ba._general import Call from ba._generated.enums import TimeType - _ba.timer(2.0, - lambda: _ba.screenmessage( - _ba.app.lang.get_resource('internal.corruptFileText'). - replace('${EMAIL}', 'support@froemling.net'), - color=(1, 0, 0), - ), - timetype=TimeType.REAL) - _ba.timer(2.0, - Call(_ba.playsound, _ba.getsound('error')), - timetype=TimeType.REAL) + + _ba.timer( + 2.0, + lambda: _ba.screenmessage( + _ba.app.lang.get_resource('internal.corruptFileText').replace( + '${EMAIL}', 'support@froemling.net' + ), + color=(1, 0, 0), + ), + timetype=TimeType.REAL, + ) + _ba.timer( + 2.0, Call(_ba.playsound, _ba.getsound('error')), timetype=TimeType.REAL + ) diff --git a/dist/ba_data/python/ba/_assetmanager.py b/dist/ba_data/python/ba/_assetmanager.py index e57c921..2e48619 100644 --- a/dist/ba_data/python/ba/_assetmanager.py +++ b/dist/ba_data/python/ba/_assetmanager.py @@ -15,8 +15,12 @@ import time import os import sys -from efro.dataclassio import (ioprepped, IOAttrs, dataclass_from_json, - dataclass_to_json) +from efro.dataclassio import ( + ioprepped, + IOAttrs, + dataclass_from_json, + dataclass_to_json, +) import _ba if TYPE_CHECKING: @@ -34,8 +38,9 @@ class FileValue: class State: """Holds all persistent state for the asset-manager.""" - files: Annotated[dict[str, FileValue], - IOAttrs('files')] = field(default_factory=dict) + files: Annotated[dict[str, FileValue], IOAttrs('files')] = field( + default_factory=dict + ) class AssetManager: @@ -66,8 +71,14 @@ class AssetManager: account_token: str, ) -> AssetGather: """Spawn an asset-gather operation from this manager.""" - print('would gather', packages, 'and flavor', flavor, 'with token', - account_token) + print( + 'would gather', + packages, + 'and flavor', + flavor, + 'with token', + account_token, + ) return AssetGather(self) def update(self) -> None: @@ -162,9 +173,7 @@ class AssetGather: def fetch_url(url: str, filename: Path, asset_gather: AssetGather) -> None: - """Fetch a given url to a given filename for a given AssetGather. - - """ + """Fetch a given url to a given filename for a given AssetGather.""" # pylint: disable=consider-using-with import socket @@ -175,9 +184,7 @@ def fetch_url(url: str, filename: Path, asset_gather: AssetGather) -> None: # Pass a very short timeout to urllib so we have opportunities # to cancel even with network blockage. - req = urllib.request.urlopen(url, - context=_ba.app.net.sslcontext, - timeout=1) + req = urllib.request.urlopen(url, context=_ba.app.net.sslcontext, timeout=1) file_size = int(req.headers['Content-Length']) print(f'\nDownloading: {filename} Bytes: {file_size:,}') @@ -200,6 +207,7 @@ def fetch_url(url: str, filename: Path, asset_gather: AssetGather) -> None: data = req.read(block_sz) except ValueError: import traceback + traceback.print_exc() print('VALUEERROR', flush=True) break @@ -210,8 +218,10 @@ def fetch_url(url: str, filename: Path, asset_gather: AssetGather) -> None: print('\n\n\nsorry -- try back later') os.unlink(filename) raise - print('\nHmmm... little issue... ' - 'I\'ll wait a couple of seconds') + print( + '\nHmmm... little issue... ' + 'I\'ll wait a couple of seconds' + ) time.sleep(3) time_outs += 1 continue diff --git a/dist/ba_data/python/ba/_asyncio.py b/dist/ba_data/python/ba/_asyncio.py index 3639d2a..5dbf8ed 100644 --- a/dist/ba_data/python/ba/_asyncio.py +++ b/dist/ba_data/python/ba/_asyncio.py @@ -69,13 +69,14 @@ def setup_asyncio() -> asyncio.AbstractEventLoop: if duration > warn_time: logging.warning( 'Asyncio loop step took %.4fs; ideal max is %.4f', - duration, warn_time) + duration, + warn_time, + ) global _asyncio_timer # pylint: disable=invalid-name - _asyncio_timer = _ba.Timer(1.0 / 30.0, - run_cycle, - timetype=TimeType.REAL, - repeat=True) + _asyncio_timer = _ba.Timer( + 1.0 / 30.0, run_cycle, timetype=TimeType.REAL, repeat=True + ) if bool(False): diff --git a/dist/ba_data/python/ba/_benchmark.py b/dist/ba_data/python/ba/_benchmark.py index 119296c..f882165 100644 --- a/dist/ba_data/python/ba/_benchmark.py +++ b/dist/ba_data/python/ba/_benchmark.py @@ -50,31 +50,42 @@ def run_cpu_benchmark() -> None: _ba.new_host_session(BenchmarkSession, benchmark_type='cpu') -def run_stress_test(playlist_type: str = 'Random', - playlist_name: str = '__default__', - player_count: int = 8, - round_duration: int = 30) -> None: +def run_stress_test( + playlist_type: str = 'Random', + playlist_name: str = '__default__', + player_count: int = 8, + round_duration: int = 30, +) -> None: """Run a stress test.""" from ba import modutils from ba._general import Call from ba._generated.enums import TimeType + _ba.screenmessage( - 'Beginning stress test.. use ' - "'End Game' to stop testing.", - color=(1, 1, 0)) + "Beginning stress test.. use 'End Test' to stop testing.", + color=(1, 1, 0), + ) with _ba.Context('ui'): - start_stress_test({ - 'playlist_type': playlist_type, - 'playlist_name': playlist_name, - 'player_count': player_count, - 'round_duration': round_duration - }) - _ba.timer(7.0, - Call(_ba.screenmessage, - ('stats will be written to ' + - modutils.get_human_readable_user_scripts_path() + - '/stress_test_stats.csv')), - timetype=TimeType.REAL) + start_stress_test( + { + 'playlist_type': playlist_type, + 'playlist_name': playlist_name, + 'player_count': player_count, + 'round_duration': round_duration, + } + ) + _ba.timer( + 7.0, + Call( + _ba.screenmessage, + ( + 'stats will be written to ' + + modutils.get_human_readable_user_scripts_path() + + '/stress_test_stats.csv' + ), + ), + timetype=TimeType.REAL, + ) def stop_stress_test() -> None: @@ -94,6 +105,7 @@ def start_stress_test(args: dict[str, Any]) -> None: from ba._dualteamsession import DualTeamSession from ba._freeforallsession import FreeForAllSession from ba._generated.enums import TimeType, TimeFormat + appconfig = _ba.app.config playlist_type = args['playlist_type'] if playlist_type == 'Random': @@ -101,33 +113,42 @@ def start_stress_test(args: dict[str, Any]) -> None: playlist_type = 'Teams' else: playlist_type = 'Free-For-All' - _ba.screenmessage('Running Stress Test (listType="' + playlist_type + - '", listName="' + args['playlist_name'] + '")...') + _ba.screenmessage( + 'Running Stress Test (listType="' + + playlist_type + + '", listName="' + + args['playlist_name'] + + '")...' + ) if playlist_type == 'Teams': appconfig['Team Tournament Playlist Selection'] = args['playlist_name'] appconfig['Team Tournament Playlist Randomize'] = 1 - _ba.timer(1.0, - Call(_ba.pushcall, Call(_ba.new_host_session, - DualTeamSession)), - timetype=TimeType.REAL) + _ba.timer( + 1.0, + Call(_ba.pushcall, Call(_ba.new_host_session, DualTeamSession)), + timetype=TimeType.REAL, + ) else: appconfig['Free-for-All Playlist Selection'] = args['playlist_name'] appconfig['Free-for-All Playlist Randomize'] = 1 - _ba.timer(1.0, - Call(_ba.pushcall, - Call(_ba.new_host_session, FreeForAllSession)), - timetype=TimeType.REAL) + _ba.timer( + 1.0, + Call(_ba.pushcall, Call(_ba.new_host_session, FreeForAllSession)), + timetype=TimeType.REAL, + ) _ba.set_stress_testing(True, args['player_count']) _ba.app.stress_test_reset_timer = _ba.Timer( args['round_duration'] * 1000, Call(_reset_stress_test, args), timetype=TimeType.REAL, - timeformat=TimeFormat.MILLISECONDS) + timeformat=TimeFormat.MILLISECONDS, + ) def _reset_stress_test(args: dict[str, Any]) -> None: from ba._general import Call from ba._generated.enums import TimeType + _ba.set_stress_testing(False, args['player_count']) _ba.screenmessage('Resetting stress test...') session = _ba.get_foreground_host_session() @@ -138,34 +159,40 @@ def _reset_stress_test(args: dict[str, Any]) -> None: def run_gpu_benchmark() -> None: """Kick off a benchmark to test gpu speeds.""" - _ba.screenmessage('FIXME: Not wired up yet.', color=(1, 0, 0)) + # FIXME: Not wired up yet. + _ba.screenmessage('Not wired up yet.', color=(1, 0, 0)) def run_media_reload_benchmark() -> None: """Kick off a benchmark to test media reloading speeds.""" from ba._general import Call from ba._generated.enums import TimeType + _ba.reload_media() _ba.show_progress_bar() def delay_add(start_time: float) -> None: - def doit(start_time_2: float) -> None: _ba.screenmessage( _ba.app.lang.get_resource( - 'debugWindow.totalReloadTimeText').replace( - '${TIME}', - str(_ba.time(TimeType.REAL) - start_time_2))) + 'debugWindow.totalReloadTimeText' + ).replace( + '${TIME}', str(_ba.time(TimeType.REAL) - start_time_2) + ) + ) _ba.print_load_info() if _ba.app.config.resolve('Texture Quality') != 'High': - _ba.screenmessage(_ba.app.lang.get_resource( - 'debugWindow.reloadBenchmarkBestResultsText'), - color=(1, 1, 0)) + _ba.screenmessage( + _ba.app.lang.get_resource( + 'debugWindow.reloadBenchmarkBestResultsText' + ), + color=(1, 1, 0), + ) _ba.add_clean_frame_callback(Call(doit, start_time)) # The reload starts (should add a completion callback to the # reload func to fix this). - _ba.timer(0.05, - Call(delay_add, _ba.time(TimeType.REAL)), - timetype=TimeType.REAL) + _ba.timer( + 0.05, Call(delay_add, _ba.time(TimeType.REAL)), timetype=TimeType.REAL + ) diff --git a/dist/ba_data/python/ba/_bootstrap.py b/dist/ba_data/python/ba/_bootstrap.py index 6fe2b55..5766328 100644 --- a/dist/ba_data/python/ba/_bootstrap.py +++ b/dist/ba_data/python/ba/_bootstrap.py @@ -33,11 +33,13 @@ def bootstrap() -> None: # Python's stdout/stderr into it. Then we can at least debug problems # on systems where native stdout/stderr is not easily accessible # such as Android. - log_handler = setup_logging(log_path=None, - level=LogLevel.DEBUG, - suppress_non_root_debug=True, - log_stdout_stderr=True, - cache_size_limit=1024 * 1024) + log_handler = setup_logging( + log_path=None, + level=LogLevel.DEBUG, + suppress_non_root_debug=True, + log_stdout_stderr=True, + cache_size_limit=1024 * 1024, + ) log_handler.add_callback(_on_log) @@ -45,7 +47,7 @@ def bootstrap() -> None: # Give a soft warning if we're being used with a different binary # version than we expect. - expected_build = 20887 + expected_build = 20921 running_build: int = env['build_number'] if running_build != expected_build: print( @@ -53,7 +55,8 @@ def bootstrap() -> None: f' Ballistica build {expected_build}.\n' f' You are running build {running_build}.' f' This might cause the app to error or misbehave.', - file=sys.stderr) + file=sys.stderr, + ) # In bootstrap_monolithic.py we told Python not to handle SIGINT itself # (because that must be done in the main thread). Now we finish the @@ -69,7 +72,8 @@ def bootstrap() -> None: print( 'ERROR: Python\'s UTF-8 mode is not set.' ' This will likely result in errors.', - file=sys.stderr) + file=sys.stderr, + ) debug_build = env['debug_build'] @@ -78,20 +82,24 @@ def bootstrap() -> None: print( f'WARNING: Mismatch in debug_build {debug_build}' f' and sys.flags.dev_mode {sys.flags.dev_mode}', - file=sys.stderr) + file=sys.stderr, + ) # In embedded situations (when we're providing our own Python) let's # also provide our own root certs so ssl works. We can consider overriding # this in particular embedded cases if we can verify that system certs # are working. # (We also allow forcing this via an env var if the user desires) - if (_ba.contains_python_dist() - or os.environ.get('BA_USE_BUNDLED_ROOT_CERTS') == '1'): + if ( + _ba.contains_python_dist() + or os.environ.get('BA_USE_BUNDLED_ROOT_CERTS') == '1' + ): import certifi # Let both OpenSSL and requests (if present) know to use this. - os.environ['SSL_CERT_FILE'] = os.environ['REQUESTS_CA_BUNDLE'] = ( - certifi.where()) + os.environ['SSL_CERT_FILE'] = os.environ[ + 'REQUESTS_CA_BUNDLE' + ] = certifi.where() # On Windows I'm seeing the following error creating asyncio loops in # background threads with the default proactor setup: @@ -104,6 +112,7 @@ def bootstrap() -> None: # to default to selector in that case?.. if sys.platform == 'win32': import asyncio + asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) # pylint: disable=c-extension-no-member @@ -122,6 +131,7 @@ def bootstrap() -> None: # Now spin up our App instance and store it on both _ba and ba. from ba._app import App import ba + _ba.app = ba.app = App() _ba.app.log_handler = log_handler @@ -138,6 +148,7 @@ class _CustomHelper: # (but then things mostly work). Let's get the ugly error out # of the way explicitly. import sysconfig + try: # This errors once but seems to run cleanly after, so let's # get the error out of the way. @@ -146,13 +157,16 @@ class _CustomHelper: pass import pydoc + # Disable pager and interactive help since neither works well # with our funky multi-threaded setup or in-game/cloud consoles. # Let's just do simple text dumps. pydoc.pager = pydoc.plainpager if not args and not kwds: - print('Interactive help is not available in this environment.\n' - 'Type help(object) for help about object.') + print( + 'Interactive help is not available in this environment.\n' + 'Type help(object) for help about object.' + ) return None return pydoc.help(*args, **kwds) @@ -170,6 +184,8 @@ def _on_log(entry: LogEntry) -> None: # We also want to feed some logs to the old V1-cloud-log system. # Let's go with anything warning or higher as well as the stdout/stderr # log messages that ba.app.log_handler creates for us. - if entry.level.value >= LogLevel.WARNING.value or entry.name in ('stdout', - 'stderr'): + if entry.level.value >= LogLevel.WARNING.value or entry.name in ( + 'stdout', + 'stderr', + ): _ba.v1_cloud_log(entry.message) diff --git a/dist/ba_data/python/ba/_campaign.py b/dist/ba_data/python/ba/_campaign.py index 4b79c66..b0242ff 100644 --- a/dist/ba_data/python/ba/_campaign.py +++ b/dist/ba_data/python/ba/_campaign.py @@ -28,10 +28,12 @@ class Campaign: Category: **App Classes** """ - def __init__(self, - name: str, - sequential: bool = True, - levels: list[ba.Level] | None = None): + def __init__( + self, + name: str, + sequential: bool = True, + levels: list[ba.Level] | None = None, + ): self._name = name self._sequential = sequential self._levels: list[ba.Level] = [] @@ -67,12 +69,13 @@ class Campaign: def getlevel(self, name: str) -> ba.Level: """Return a contained ba.Level by name.""" from ba import _error + for level in self._levels: if level.name == name: return level - raise _error.NotFoundError("Level '" + name + - "' not found in campaign '" + self.name + - "'") + raise _error.NotFoundError( + "Level '" + name + "' not found in campaign '" + self.name + "'" + ) def reset(self) -> None: """Reset state for the Campaign.""" @@ -91,9 +94,9 @@ class Campaign: @property def configdict(self) -> dict[str, Any]: """Return the live config dict for this campaign.""" - val: dict[str, Any] = (_ba.app.config.setdefault('Campaigns', - {}).setdefault( - self._name, {})) + val: dict[str, Any] = _ba.app.config.setdefault( + 'Campaigns', {} + ).setdefault(self._name, {}) assert isinstance(val, dict) return val @@ -121,92 +124,132 @@ def init_campaigns() -> None: Campaign( 'Easy', levels=[ - Level('Onslaught Training', - gametype=OnslaughtGame, - settings={'preset': 'training_easy'}, - preview_texture_name='doomShroomPreview'), - Level('Rookie Onslaught', - gametype=OnslaughtGame, - settings={'preset': 'rookie_easy'}, - preview_texture_name='courtyardPreview'), - Level('Rookie Football', - gametype=FootballCoopGame, - settings={'preset': 'rookie_easy'}, - preview_texture_name='footballStadiumPreview'), - Level('Pro Onslaught', - gametype=OnslaughtGame, - settings={'preset': 'pro_easy'}, - preview_texture_name='doomShroomPreview'), - Level('Pro Football', - gametype=FootballCoopGame, - settings={'preset': 'pro_easy'}, - preview_texture_name='footballStadiumPreview'), - Level('Pro Runaround', - gametype=RunaroundGame, - settings={'preset': 'pro_easy'}, - preview_texture_name='towerDPreview'), - Level('Uber Onslaught', - gametype=OnslaughtGame, - settings={'preset': 'uber_easy'}, - preview_texture_name='courtyardPreview'), - Level('Uber Football', - gametype=FootballCoopGame, - settings={'preset': 'uber_easy'}, - preview_texture_name='footballStadiumPreview'), - Level('Uber Runaround', - gametype=RunaroundGame, - settings={'preset': 'uber_easy'}, - preview_texture_name='towerDPreview') + Level( + 'Onslaught Training', + gametype=OnslaughtGame, + settings={'preset': 'training_easy'}, + preview_texture_name='doomShroomPreview', + ), + Level( + 'Rookie Onslaught', + gametype=OnslaughtGame, + settings={'preset': 'rookie_easy'}, + preview_texture_name='courtyardPreview', + ), + Level( + 'Rookie Football', + gametype=FootballCoopGame, + settings={'preset': 'rookie_easy'}, + preview_texture_name='footballStadiumPreview', + ), + Level( + 'Pro Onslaught', + gametype=OnslaughtGame, + settings={'preset': 'pro_easy'}, + preview_texture_name='doomShroomPreview', + ), + Level( + 'Pro Football', + gametype=FootballCoopGame, + settings={'preset': 'pro_easy'}, + preview_texture_name='footballStadiumPreview', + ), + Level( + 'Pro Runaround', + gametype=RunaroundGame, + settings={'preset': 'pro_easy'}, + preview_texture_name='towerDPreview', + ), + Level( + 'Uber Onslaught', + gametype=OnslaughtGame, + settings={'preset': 'uber_easy'}, + preview_texture_name='courtyardPreview', + ), + Level( + 'Uber Football', + gametype=FootballCoopGame, + settings={'preset': 'uber_easy'}, + preview_texture_name='footballStadiumPreview', + ), + Level( + 'Uber Runaround', + gametype=RunaroundGame, + settings={'preset': 'uber_easy'}, + preview_texture_name='towerDPreview', + ), ], - )) + ) + ) # "hard" mode register_campaign( Campaign( 'Default', levels=[ - Level('Onslaught Training', - gametype=OnslaughtGame, - settings={'preset': 'training'}, - preview_texture_name='doomShroomPreview'), - Level('Rookie Onslaught', - gametype=OnslaughtGame, - settings={'preset': 'rookie'}, - preview_texture_name='courtyardPreview'), - Level('Rookie Football', - gametype=FootballCoopGame, - settings={'preset': 'rookie'}, - preview_texture_name='footballStadiumPreview'), - Level('Pro Onslaught', - gametype=OnslaughtGame, - settings={'preset': 'pro'}, - preview_texture_name='doomShroomPreview'), - Level('Pro Football', - gametype=FootballCoopGame, - settings={'preset': 'pro'}, - preview_texture_name='footballStadiumPreview'), - Level('Pro Runaround', - gametype=RunaroundGame, - settings={'preset': 'pro'}, - preview_texture_name='towerDPreview'), - Level('Uber Onslaught', - gametype=OnslaughtGame, - settings={'preset': 'uber'}, - preview_texture_name='courtyardPreview'), - Level('Uber Football', - gametype=FootballCoopGame, - settings={'preset': 'uber'}, - preview_texture_name='footballStadiumPreview'), - Level('Uber Runaround', - gametype=RunaroundGame, - settings={'preset': 'uber'}, - preview_texture_name='towerDPreview'), - Level('The Last Stand', - gametype=TheLastStandGame, - settings={}, - preview_texture_name='rampagePreview') + Level( + 'Onslaught Training', + gametype=OnslaughtGame, + settings={'preset': 'training'}, + preview_texture_name='doomShroomPreview', + ), + Level( + 'Rookie Onslaught', + gametype=OnslaughtGame, + settings={'preset': 'rookie'}, + preview_texture_name='courtyardPreview', + ), + Level( + 'Rookie Football', + gametype=FootballCoopGame, + settings={'preset': 'rookie'}, + preview_texture_name='footballStadiumPreview', + ), + Level( + 'Pro Onslaught', + gametype=OnslaughtGame, + settings={'preset': 'pro'}, + preview_texture_name='doomShroomPreview', + ), + Level( + 'Pro Football', + gametype=FootballCoopGame, + settings={'preset': 'pro'}, + preview_texture_name='footballStadiumPreview', + ), + Level( + 'Pro Runaround', + gametype=RunaroundGame, + settings={'preset': 'pro'}, + preview_texture_name='towerDPreview', + ), + Level( + 'Uber Onslaught', + gametype=OnslaughtGame, + settings={'preset': 'uber'}, + preview_texture_name='courtyardPreview', + ), + Level( + 'Uber Football', + gametype=FootballCoopGame, + settings={'preset': 'uber'}, + preview_texture_name='footballStadiumPreview', + ), + Level( + 'Uber Runaround', + gametype=RunaroundGame, + settings={'preset': 'uber'}, + preview_texture_name='towerDPreview', + ), + Level( + 'The Last Stand', + gametype=TheLastStandGame, + settings={}, + preview_texture_name='rampagePreview', + ), ], - )) + ) + ) # challenges: our 'official' random extra co-op levels register_campaign( @@ -214,121 +257,153 @@ def init_campaigns() -> None: 'Challenges', sequential=False, levels=[ - Level('Infinite Onslaught', - gametype=OnslaughtGame, - settings={'preset': 'endless'}, - preview_texture_name='doomShroomPreview'), - Level('Infinite Runaround', - gametype=RunaroundGame, - settings={'preset': 'endless'}, - preview_texture_name='towerDPreview'), - Level('Race', - displayname='${GAME}', - gametype=RaceGame, - settings={ - 'map': 'Big G', - 'Laps': 3, - 'Bomb Spawning': 0 - }, - preview_texture_name='bigGPreview'), - Level('Pro Race', - displayname='Pro ${GAME}', - gametype=RaceGame, - settings={ - 'map': 'Big G', - 'Laps': 3, - 'Bomb Spawning': 1000 - }, - preview_texture_name='bigGPreview'), - Level('Lake Frigid Race', - displayname='${GAME}', - gametype=RaceGame, - settings={ - 'map': 'Lake Frigid', - 'Laps': 6, - 'Mine Spawning': 2000, - 'Bomb Spawning': 0 - }, - preview_texture_name='lakeFrigidPreview'), - Level('Football', - displayname='${GAME}', - gametype=FootballCoopGame, - settings={'preset': 'tournament'}, - preview_texture_name='footballStadiumPreview'), - Level('Pro Football', - displayname='Pro ${GAME}', - gametype=FootballCoopGame, - settings={'preset': 'tournament_pro'}, - preview_texture_name='footballStadiumPreview'), - Level('Runaround', - displayname='${GAME}', - gametype=RunaroundGame, - settings={'preset': 'tournament'}, - preview_texture_name='towerDPreview'), - Level('Uber Runaround', - displayname='Uber ${GAME}', - gametype=RunaroundGame, - settings={'preset': 'tournament_uber'}, - preview_texture_name='towerDPreview'), - Level('The Last Stand', - displayname='${GAME}', - gametype=TheLastStandGame, - settings={'preset': 'tournament'}, - preview_texture_name='rampagePreview'), - Level('Tournament Infinite Onslaught', - displayname='Infinite Onslaught', - gametype=OnslaughtGame, - settings={'preset': 'endless_tournament'}, - preview_texture_name='doomShroomPreview'), - Level('Tournament Infinite Runaround', - displayname='Infinite Runaround', - gametype=RunaroundGame, - settings={'preset': 'endless_tournament'}, - preview_texture_name='towerDPreview'), - Level('Target Practice', - displayname='Pro ${GAME}', - gametype=TargetPracticeGame, - settings={}, - preview_texture_name='doomShroomPreview'), - Level('Target Practice B', - displayname='${GAME}', - gametype=TargetPracticeGame, - settings={ - 'Target Count': 2, - 'Enable Impact Bombs': False, - 'Enable Triple Bombs': False - }, - preview_texture_name='doomShroomPreview'), - Level('Meteor Shower', - displayname='${GAME}', - gametype=MeteorShowerGame, - settings={}, - preview_texture_name='rampagePreview'), - Level('Epic Meteor Shower', - displayname='${GAME}', - gametype=MeteorShowerGame, - settings={'Epic Mode': True}, - preview_texture_name='rampagePreview'), - Level('Easter Egg Hunt', - displayname='${GAME}', - gametype=EasterEggHuntGame, - settings={}, - preview_texture_name='towerDPreview'), - Level('Pro Easter Egg Hunt', - displayname='Pro ${GAME}', - gametype=EasterEggHuntGame, - settings={'Pro Mode': True}, - preview_texture_name='towerDPreview'), + Level( + 'Infinite Onslaught', + gametype=OnslaughtGame, + settings={'preset': 'endless'}, + preview_texture_name='doomShroomPreview', + ), + Level( + 'Infinite Runaround', + gametype=RunaroundGame, + settings={'preset': 'endless'}, + preview_texture_name='towerDPreview', + ), + Level( + 'Race', + displayname='${GAME}', + gametype=RaceGame, + settings={'map': 'Big G', 'Laps': 3, 'Bomb Spawning': 0}, + preview_texture_name='bigGPreview', + ), + Level( + 'Pro Race', + displayname='Pro ${GAME}', + gametype=RaceGame, + settings={'map': 'Big G', 'Laps': 3, 'Bomb Spawning': 1000}, + preview_texture_name='bigGPreview', + ), + Level( + 'Lake Frigid Race', + displayname='${GAME}', + gametype=RaceGame, + settings={ + 'map': 'Lake Frigid', + 'Laps': 6, + 'Mine Spawning': 2000, + 'Bomb Spawning': 0, + }, + preview_texture_name='lakeFrigidPreview', + ), + Level( + 'Football', + displayname='${GAME}', + gametype=FootballCoopGame, + settings={'preset': 'tournament'}, + preview_texture_name='footballStadiumPreview', + ), + Level( + 'Pro Football', + displayname='Pro ${GAME}', + gametype=FootballCoopGame, + settings={'preset': 'tournament_pro'}, + preview_texture_name='footballStadiumPreview', + ), + Level( + 'Runaround', + displayname='${GAME}', + gametype=RunaroundGame, + settings={'preset': 'tournament'}, + preview_texture_name='towerDPreview', + ), + Level( + 'Uber Runaround', + displayname='Uber ${GAME}', + gametype=RunaroundGame, + settings={'preset': 'tournament_uber'}, + preview_texture_name='towerDPreview', + ), + Level( + 'The Last Stand', + displayname='${GAME}', + gametype=TheLastStandGame, + settings={'preset': 'tournament'}, + preview_texture_name='rampagePreview', + ), + Level( + 'Tournament Infinite Onslaught', + displayname='Infinite Onslaught', + gametype=OnslaughtGame, + settings={'preset': 'endless_tournament'}, + preview_texture_name='doomShroomPreview', + ), + Level( + 'Tournament Infinite Runaround', + displayname='Infinite Runaround', + gametype=RunaroundGame, + settings={'preset': 'endless_tournament'}, + preview_texture_name='towerDPreview', + ), + Level( + 'Target Practice', + displayname='Pro ${GAME}', + gametype=TargetPracticeGame, + settings={}, + preview_texture_name='doomShroomPreview', + ), + Level( + 'Target Practice B', + displayname='${GAME}', + gametype=TargetPracticeGame, + settings={ + 'Target Count': 2, + 'Enable Impact Bombs': False, + 'Enable Triple Bombs': False, + }, + preview_texture_name='doomShroomPreview', + ), + Level( + 'Meteor Shower', + displayname='${GAME}', + gametype=MeteorShowerGame, + settings={}, + preview_texture_name='rampagePreview', + ), + Level( + 'Epic Meteor Shower', + displayname='${GAME}', + gametype=MeteorShowerGame, + settings={'Epic Mode': True}, + preview_texture_name='rampagePreview', + ), + Level( + 'Easter Egg Hunt', + displayname='${GAME}', + gametype=EasterEggHuntGame, + settings={}, + preview_texture_name='towerDPreview', + ), + Level( + 'Pro Easter Egg Hunt', + displayname='Pro ${GAME}', + gametype=EasterEggHuntGame, + settings={'Pro Mode': True}, + preview_texture_name='towerDPreview', + ), Level( name='Ninja Fight', # (unique id not seen by player) displayname='${GAME}', # (readable name seen by player) gametype=NinjaFightGame, settings={'preset': 'regular'}, - preview_texture_name='courtyardPreview'), - Level(name='Pro Ninja Fight', - displayname='Pro ${GAME}', - gametype=NinjaFightGame, - settings={'preset': 'pro'}, - preview_texture_name='courtyardPreview') + preview_texture_name='courtyardPreview', + ), + Level( + name='Pro Ninja Fight', + displayname='Pro ${GAME}', + gametype=NinjaFightGame, + settings={'preset': 'pro'}, + preview_texture_name='courtyardPreview', + ), ], - )) + ) + ) diff --git a/dist/ba_data/python/ba/_cloud.py b/dist/ba_data/python/ba/_cloud.py index ebbd99a..029ed48 100644 --- a/dist/ba_data/python/ba/_cloud.py +++ b/dist/ba_data/python/ba/_cloud.py @@ -35,7 +35,8 @@ class CloudSubsystem: self, msg: bacommon.cloud.LoginProxyRequestMessage, on_response: Callable[ - [bacommon.cloud.LoginProxyRequestResponse | Exception], None], + [bacommon.cloud.LoginProxyRequestResponse | Exception], None + ], ) -> None: ... @@ -44,7 +45,8 @@ class CloudSubsystem: self, msg: bacommon.cloud.LoginProxyStateQueryMessage, on_response: Callable[ - [bacommon.cloud.LoginProxyStateQueryResponse | Exception], None], + [bacommon.cloud.LoginProxyStateQueryResponse | Exception], None + ], ) -> None: ... @@ -75,11 +77,15 @@ class CloudSubsystem: and passed either the response or the error that occurred. """ from ba._general import Call + del msg # Unused. _ba.pushcall( - Call(on_response, - RuntimeError('Cloud functionality is not available.'))) + Call( + on_response, + RuntimeError('Cloud functionality is not available.'), + ) + ) @overload def send_message( @@ -89,8 +95,8 @@ class CloudSubsystem: @overload def send_message( - self, - msg: bacommon.cloud.TestMessage) -> bacommon.cloud.TestResponse: + self, msg: bacommon.cloud.TestMessage + ) -> bacommon.cloud.TestResponse: ... def send_message(self, msg: Message) -> Response | None: @@ -107,6 +113,7 @@ def cloud_console_exec(code: str) -> None: import logging import __main__ from ba._generated.enums import TimeType + try: # First try it as eval. @@ -118,7 +125,8 @@ def cloud_console_exec(code: str) -> None: # hmm; when we can't compile it as eval will we always get # syntax error? logging.exception( - 'unexpected error compiling code for cloud-console eval.') + 'unexpected error compiling code for cloud-console eval.' + ) evalcode = None if evalcode is not None: # pylint: disable=eval-used @@ -135,6 +143,7 @@ def cloud_console_exec(code: str) -> None: exec(execcode, vars(__main__), vars(__main__)) except Exception: import traceback + apptime = _ba.time(TimeType.REAL) print(f'Exec error at time {apptime:.2f}.', file=sys.stderr) traceback.print_exc() diff --git a/dist/ba_data/python/ba/_coopgame.py b/dist/ba_data/python/ba/_coopgame.py index 6e380d8..0df8beb 100644 --- a/dist/ba_data/python/ba/_coopgame.py +++ b/dist/ba_data/python/ba/_coopgame.py @@ -33,6 +33,7 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]): @classmethod def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool: from ba._coopsession import CoopSession + return issubclass(sessiontype, CoopSession) def __init__(self, settings: dict): @@ -55,12 +56,14 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]): # Preload achievement images in case we get some. _ba.timer(2.0, WeakCall(self._preload_achievements)) - def _show_standard_scores_to_beat_ui(self, - scores: list[dict[str, Any]]) -> None: + def _show_standard_scores_to_beat_ui( + self, scores: list[dict[str, Any]] + ) -> None: from efro.util import asserttype from ba._gameutils import timestring, animate from ba._nodeactor import NodeActor from ba._generated.enums import TimeFormat + display_type = self.get_score_type() if scores is not None: @@ -70,26 +73,35 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]): # Now make a display for the most recent challenge. for score in scores: if score['type'] == 'score_challenge': - tval = (score['player'] + ': ' + timestring( - int(score['value']) * 10, - timeformat=TimeFormat.MILLISECONDS).evaluate() - if display_type == 'time' else str(score['value'])) + tval = ( + score['player'] + + ': ' + + timestring( + int(score['value']) * 10, + timeformat=TimeFormat.MILLISECONDS, + ).evaluate() + if display_type == 'time' + else str(score['value']) + ) hattach = 'center' if display_type == 'time' else 'left' halign = 'center' if display_type == 'time' else 'left' pos = (20, -70) if display_type == 'time' else (20, -130) txt = NodeActor( - _ba.newnode('text', - attrs={ - 'v_attach': 'top', - 'h_attach': hattach, - 'h_align': halign, - 'color': (0.7, 0.4, 1, 1), - 'shadow': 0.5, - 'flatness': 1.0, - 'position': pos, - 'scale': 0.6, - 'text': tval - })).autoretain() + _ba.newnode( + 'text', + attrs={ + 'v_attach': 'top', + 'h_attach': hattach, + 'h_align': halign, + 'color': (0.7, 0.4, 1, 1), + 'shadow': 0.5, + 'flatness': 1.0, + 'position': pos, + 'scale': 0.6, + 'text': tval, + }, + ) + ).autoretain() assert txt.node is not None animate(txt.node, 'scale', {1.0: 0.0, 1.1: 0.7, 1.2: 0.6}) break @@ -104,8 +116,7 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]): def _get_coop_level_name(self) -> str: assert self.session.campaign is not None - return self.session.campaign.name + ':' + str( - self.settings_raw['name']) + return self.session.campaign.name + ':' + str(self.settings_raw['name']) def celebrate(self, duration: float) -> None: """Tells all existing player-controlled characters to celebrate. @@ -115,13 +126,15 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]): duration is given in seconds. """ from ba._messages import CelebrateMessage + for player in self.players: if player.actor: player.actor.handlemessage(CelebrateMessage(duration)) def _preload_achievements(self) -> None: achievements = _ba.app.ach.achievements_for_coop_level( - self._get_coop_level_name()) + self._get_coop_level_name() + ) for ach in achievements: ach.get_icon_texture(True) @@ -129,43 +142,52 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]): # pylint: disable=cyclic-import from ba._language import Lstr from bastd.actor.text import Text + ts_h_offs = 30 v_offs = -200 achievements = [ - a for a in _ba.app.ach.achievements_for_coop_level( - self._get_coop_level_name()) if not a.complete + a + for a in _ba.app.ach.achievements_for_coop_level( + self._get_coop_level_name() + ) + if not a.complete ] vrmode = _ba.app.vr_mode if achievements: - Text(Lstr(resource='achievementsRemainingText'), - host_only=True, - position=(ts_h_offs - 10 + 40, v_offs - 10), - transition=Text.Transition.FADE_IN, - scale=1.1, - h_attach=Text.HAttach.LEFT, - v_attach=Text.VAttach.TOP, - color=(1, 1, 1.2, 1) if vrmode else (0.8, 0.8, 1.0, 1.0), - flatness=1.0 if vrmode else 0.6, - shadow=1.0 if vrmode else 0.5, - transition_delay=0.0, - transition_out_delay=1.3 - if self.slow_motion else 4.0).autoretain() + Text( + Lstr(resource='achievementsRemainingText'), + host_only=True, + position=(ts_h_offs - 10 + 40, v_offs - 10), + transition=Text.Transition.FADE_IN, + scale=1.1, + h_attach=Text.HAttach.LEFT, + v_attach=Text.VAttach.TOP, + color=(1, 1, 1.2, 1) if vrmode else (0.8, 0.8, 1.0, 1.0), + flatness=1.0 if vrmode else 0.6, + shadow=1.0 if vrmode else 0.5, + transition_delay=0.0, + transition_out_delay=1.3 if self.slow_motion else 4.0, + ).autoretain() hval = 70 vval = -50 tdelay = 0.0 for ach in achievements: tdelay += 0.05 - ach.create_display(hval + 40, - vval + v_offs, - 0 + tdelay, - outdelay=1.3 if self.slow_motion else 4.0, - style='in_game') + ach.create_display( + hval + 40, + vval + v_offs, + 0 + tdelay, + outdelay=1.3 if self.slow_motion else 4.0, + style='in_game', + ) vval -= 55 - def spawn_player_spaz(self, - player: PlayerType, - position: Sequence[float] = (0.0, 0.0, 0.0), - angle: float | None = None) -> PlayerSpaz: + def spawn_player_spaz( + self, + player: PlayerType, + position: Sequence[float] = (0.0, 0.0, 0.0), + angle: float | None = None, + ) -> PlayerSpaz: """Spawn and wire up a standard player spaz.""" spaz = super().spawn_player_spaz(player, position, angle) @@ -173,9 +195,9 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]): spaz.play_big_death_sound = True return spaz - def _award_achievement(self, - achievement_name: str, - sound: bool = True) -> None: + def _award_achievement( + self, achievement_name: str, sound: bool = True + ) -> None: """Award an achievement. Returns True if a banner will be shown; @@ -196,6 +218,7 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]): return except Exception: from ba._error import print_exception + print_exception() # If we haven't awarded this one, check to see if we've got it. @@ -208,10 +231,9 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]): _internal.report_achievement(achievement_name) # ...and to our account. - _internal.add_transaction({ - 'type': 'ACHIEVEMENT', - 'name': achievement_name - }) + _internal.add_transaction( + {'type': 'ACHIEVEMENT', 'name': achievement_name} + ) # Now bring up a celebration banner. ach.announce_completion(sound=sound) @@ -219,14 +241,17 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]): def fade_to_red(self) -> None: """Fade the screen to red; (such as when the good guys have lost).""" from ba import _gameutils + c_existing = self.globalsnode.tint - cnode = _ba.newnode('combine', - attrs={ - 'input0': c_existing[0], - 'input1': c_existing[1], - 'input2': c_existing[2], - 'size': 3 - }) + cnode = _ba.newnode( + 'combine', + attrs={ + 'input0': c_existing[0], + 'input1': c_existing[1], + 'input2': c_existing[2], + 'size': 3, + }, + ) _gameutils.animate(cnode, 'input1', {0: c_existing[1], 2.0: 0}) _gameutils.animate(cnode, 'input2', {0: c_existing[2], 2.0: 0}) cnode.connectattr('output', self.globalsnode, 'tint') @@ -235,7 +260,8 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]): """Set up a beeping noise to play when any players are near death.""" self._life_warning_beep = None self._life_warning_beep_timer = _ba.Timer( - 1.0, WeakCall(self._update_life_warning), repeat=True) + 1.0, WeakCall(self._update_life_warning), repeat=True + ) def _update_life_warning(self) -> None: # Beep continuously if anyone is close to death. @@ -249,12 +275,16 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]): break if should_beep and self._life_warning_beep is None: from ba._nodeactor import NodeActor + self._life_warning_beep = NodeActor( - _ba.newnode('sound', - attrs={ - 'sound': self._warn_beeps_sound, - 'positional': False, - 'loop': True - })) + _ba.newnode( + 'sound', + attrs={ + 'sound': self._warn_beeps_sound, + 'positional': False, + 'loop': True, + }, + ) + ) if self._life_warning_beep is not None and not should_beep: self._life_warning_beep = None diff --git a/dist/ba_data/python/ba/_coopsession.py b/dist/ba_data/python/ba/_coopsession.py index e0698c9..0b15ccf 100644 --- a/dist/ba_data/python/ba/_coopsession.py +++ b/dist/ba_data/python/ba/_coopsession.py @@ -65,15 +65,18 @@ class CoopSession(Session): # print('FIXME: COOP SESSION WOULD CALC DEPS.') depsets: Sequence[ba.DependencySet] = [] - super().__init__(depsets, - team_names=TEAM_NAMES, - team_colors=TEAM_COLORS, - min_players=min_players, - max_players=max_players) + super().__init__( + depsets, + team_names=TEAM_NAMES, + team_colors=TEAM_COLORS, + min_players=min_players, + max_players=max_players, + ) # Tournament-ID if we correspond to a co-op tournament (otherwise None) - self.tournament_id: str | None = ( - app.coop_session_args.get('tournament_id')) + self.tournament_id: str | None = app.coop_session_args.get( + 'tournament_id' + ) self.campaign = getcampaign(app.coop_session_args['campaign']) self.campaign_level_name: str = app.coop_session_args['level'] @@ -159,10 +162,13 @@ class CoopSession(Session): # Special case: # If our current level is 'onslaught training', instantiate # our tutorial so its ready to go. (if we haven't run it yet). - if (self.campaign_level_name == 'Onslaught Training' - and self._tutorial_activity is None - and not self._ran_tutorial_activity): + if ( + self.campaign_level_name == 'Onslaught Training' + and self._tutorial_activity is None + and not self._ran_tutorial_activity + ): from bastd.tutorial import TutorialActivity + self._tutorial_activity = _ba.newactivity(TutorialActivity) def get_custom_menu_entries(self) -> list[dict[str, Any]]: @@ -170,6 +176,7 @@ class CoopSession(Session): def on_player_leave(self, sessionplayer: ba.SessionPlayer) -> None: from ba._general import WeakCall + super().on_player_leave(sessionplayer) _ba.timer(2.0, WeakCall(self._handle_empty_activity)) @@ -178,6 +185,7 @@ class CoopSession(Session): """Handle cases where all players have left the current activity.""" from ba._gameactivity import GameActivity + activity = self.getactivity() if activity is None: return # Hmm what should we do in this case? @@ -212,17 +220,21 @@ class CoopSession(Session): activity.end_game() def _on_tournament_restart_menu_press( - self, resume_callback: Callable[[], Any]) -> None: + self, resume_callback: Callable[[], Any] + ) -> None: # pylint: disable=cyclic-import from bastd.ui.tournamententry import TournamentEntryWindow from ba._gameactivity import GameActivity + activity = self.getactivity() if activity is not None and not activity.expired: assert self.tournament_id is not None assert isinstance(activity, GameActivity) - TournamentEntryWindow(tournament_id=self.tournament_id, - tournament_activity=activity, - on_close_call=resume_callback) + TournamentEntryWindow( + tournament_id=self.tournament_id, + tournament_activity=activity, + on_close_call=resume_callback, + ) def restart(self) -> None: """Restart the current game activity.""" @@ -309,9 +321,11 @@ class CoopSession(Session): # Special case: if we're coming from a joining-activity # and will be going into onslaught-training, show the # tutorial first. - if (isinstance(activity, JoinActivity) - and self.campaign_level_name == 'Onslaught Training' - and not (app.demo_mode or app.arcade_mode)): + if ( + isinstance(activity, JoinActivity) + and self.campaign_level_name == 'Onslaught Training' + and not (app.demo_mode or app.arcade_mode) + ): if self._tutorial_activity is None: raise RuntimeError('Tutorial not preloaded properly.') self.setactivity(self._tutorial_activity) @@ -336,20 +350,22 @@ class CoopSession(Session): if not (app.demo_mode or app.arcade_mode): if self.tournament_id is not None: - self._custom_menu_ui = [{ - 'label': - Lstr(resource='restartText'), - 'resume_on_call': - False, - 'call': - WeakCall(self._on_tournament_restart_menu_press - ) - }] + self._custom_menu_ui = [ + { + 'label': Lstr(resource='restartText'), + 'resume_on_call': False, + 'call': WeakCall( + self._on_tournament_restart_menu_press + ), + } + ] else: - self._custom_menu_ui = [{ - 'label': Lstr(resource='restartText'), - 'call': WeakCall(self.restart) - }] + self._custom_menu_ui = [ + { + 'label': Lstr(resource='restartText'), + 'call': WeakCall(self.restart), + } + ] # If we were in a tutorial, just pop a transition to get to the # actual round. @@ -368,10 +384,13 @@ class CoopSession(Session): playerinfos = results.playerinfos score = results.get_sessionteam_score(results.sessionteams[0]) fail_message = None - score_order = ('decreasing' - if results.lower_is_better else 'increasing') - if results.scoretype in (ScoreType.SECONDS, - ScoreType.MILLISECONDS): + score_order = ( + 'decreasing' if results.lower_is_better else 'increasing' + ) + if results.scoretype in ( + ScoreType.SECONDS, + ScoreType.MILLISECONDS, + ): scoretype = 'time' # ScoreScreen wants hundredths of a second. @@ -391,12 +410,21 @@ class CoopSession(Session): else: playerinfos = results.get('playerinfos') score = results['score'] if 'score' in results else None - fail_message = (results['fail_message'] - if 'fail_message' in results else None) - score_order = (results['score_order'] - if 'score_order' in results else 'increasing') - activity_score_type = (activity.get_score_type() if isinstance( - activity, CoopGameActivity) else None) + fail_message = ( + results['fail_message'] + if 'fail_message' in results + else None + ) + score_order = ( + results['score_order'] + if 'score_order' in results + else 'increasing' + ) + activity_score_type = ( + activity.get_score_type() + if isinstance(activity, CoopGameActivity) + else None + ) assert activity_score_type is not None scoretype = activity_score_type @@ -414,7 +442,8 @@ class CoopSession(Session): else: self.setactivity( _ba.newactivity( - CoopScoreScreen, { + CoopScoreScreen, + { 'playerinfos': playerinfos, 'score': score, 'fail_message': fail_message, @@ -422,8 +451,10 @@ class CoopSession(Session): 'score_type': scoretype, 'outcome': outcome, 'campaign': self.campaign, - 'level': self.campaign_level_name - })) + 'level': self.campaign_level_name, + }, + ) + ) # No matter what, get the next 2 levels ready to go. self._update_on_deck_game_instances() diff --git a/dist/ba_data/python/ba/_dependency.py b/dist/ba_data/python/ba/_dependency.py index d3a6d4e..a8cb462 100644 --- a/dist/ba_data/python/ba/_dependency.py +++ b/dist/ba_data/python/ba/_dependency.py @@ -5,7 +5,7 @@ from __future__ import annotations import weakref -from typing import (Generic, TypeVar, TYPE_CHECKING) +from typing import Generic, TypeVar, TYPE_CHECKING import _ba @@ -44,6 +44,7 @@ class Dependency(Generic[T]): def get_hash(self) -> int: """Return the dependency's hash, calculating it if necessary.""" from efro.util import make_hash + if self._hash is None: self._hash = make_hash((self.cls, self.config)) return self._hash @@ -52,10 +53,12 @@ class Dependency(Generic[T]): if not isinstance(obj, DependencyComponent): if obj is None: raise TypeError( - 'Dependency must be accessed through an instance.') + 'Dependency must be accessed through an instance.' + ) raise TypeError( f'Dependency cannot be added to class of type {type(obj)}' - ' (class must inherit from ba.DependencyComponent).') + ' (class must inherit from ba.DependencyComponent).' + ) # We expect to be instantiated from an already living # DependencyComponent with valid dep-data in place.. @@ -73,7 +76,8 @@ class Dependency(Generic[T]): if not depset.resolved: raise RuntimeError( - "Can't access data on an unresolved DependencySet.") + "Can't access data on an unresolved DependencySet." + ) # Look up the data in the set based on the hash for this Dependency. assert self._hash in depset.entries @@ -157,8 +161,10 @@ class DependencyEntry: component = self.component assert isinstance(component, self.cls) if component is None: - raise RuntimeError(f'Accessing DependencyComponent {self.cls} ' - 'in an invalid state.') + raise RuntimeError( + f'Accessing DependencyComponent {self.cls} ' + 'in an invalid state.' + ) return component @@ -209,6 +215,7 @@ class DependencySet(Generic[T]): ] if missing: from ba._error import DependencyError + raise DependencyError(missing) self._resolved = True @@ -278,7 +285,8 @@ class DependencySet(Generic[T]): # Grab all Dependency instances we find in the class. subdeps = [ - cls for cls in dep.cls.__dict__.values() + cls + for cls in dep.cls.__dict__.values() if isinstance(cls, Dependency) ] @@ -395,6 +403,7 @@ def test_depset() -> None: def doit() -> None: from ba._error import DependencyError + depset = DependencySet(Dependency(TestClass)) try: depset.resolve() @@ -404,7 +413,8 @@ def test_depset() -> None: print('MISSING ASSET PACKAGE', dep.config) else: raise RuntimeError( - f'Unknown dependency error for {dep.cls}') from exc + f'Unknown dependency error for {dep.cls}' + ) from exc except Exception as exc: print('DependencySet resolve failed with exc type:', type(exc)) if depset.resolved: diff --git a/dist/ba_data/python/ba/_dualteamsession.py b/dist/ba_data/python/ba/_dualteamsession.py index 0b7a327..6870fed 100644 --- a/dist/ba_data/python/ba/_dualteamsession.py +++ b/dist/ba_data/python/ba/_dualteamsession.py @@ -33,10 +33,11 @@ class DualTeamSession(MultiTeamSession): def _switch_to_score_screen(self, results: ba.GameResults) -> None: # pylint: disable=cyclic-import from bastd.activity.drawscore import DrawScoreScreenActivity - from bastd.activity.dualteamscore import ( - TeamVictoryScoreScreenActivity) + from bastd.activity.dualteamscore import TeamVictoryScoreScreenActivity from bastd.activity.multiteamvictory import ( - TeamSeriesVictoryScoreScreenActivity) + TeamSeriesVictoryScoreScreenActivity, + ) + winnergroups = results.winnergroups # If everyone has the same score, call it a draw. @@ -49,9 +50,13 @@ class DualTeamSession(MultiTeamSession): # If a team has won, show final victory screen. if winner.customdata['score'] >= (self._series_length - 1) / 2 + 1: self.setactivity( - _ba.newactivity(TeamSeriesVictoryScoreScreenActivity, - {'winner': winner})) + _ba.newactivity( + TeamSeriesVictoryScoreScreenActivity, {'winner': winner} + ) + ) else: self.setactivity( - _ba.newactivity(TeamVictoryScoreScreenActivity, - {'winner': winner})) + _ba.newactivity( + TeamVictoryScoreScreenActivity, {'winner': winner} + ) + ) diff --git a/dist/ba_data/python/ba/_error.py b/dist/ba_data/python/ba/_error.py index 5e6aba4..fb8803b 100644 --- a/dist/ba_data/python/ba/_error.py +++ b/dist/ba_data/python/ba/_error.py @@ -141,6 +141,7 @@ def print_exception(*args: Any, **keywds: Any) -> None: one time from an exact calling location. """ import traceback + if keywds: allowed_keywds = ['once'] if any(keywd not in allowed_keywds for keywd in keywds): @@ -181,6 +182,7 @@ def print_error(err_str: str, once: bool = False) -> None: one time from an exact calling location. """ import traceback + try: # If we're only printing once and already have, bail. if once: diff --git a/dist/ba_data/python/ba/_freeforallsession.py b/dist/ba_data/python/ba/_freeforallsession.py index a33c827..4be431d 100644 --- a/dist/ba_data/python/ba/_freeforallsession.py +++ b/dist/ba_data/python/ba/_freeforallsession.py @@ -18,6 +18,7 @@ class FreeForAllSession(MultiTeamSession): Category: **Gameplay Classes** """ + use_teams = False use_team_colors = False _playlist_selection_var = 'Free-for-All Playlist Selection' @@ -55,42 +56,54 @@ class FreeForAllSession(MultiTeamSession): from efro.util import asserttype from bastd.activity.drawscore import DrawScoreScreenActivity from bastd.activity.multiteamvictory import ( - TeamSeriesVictoryScoreScreenActivity) + TeamSeriesVictoryScoreScreenActivity, + ) from bastd.activity.freeforallvictory import ( - FreeForAllVictoryScoreScreenActivity) + FreeForAllVictoryScoreScreenActivity, + ) + winners = results.winnergroups # If there's multiple players and everyone has the same score, # call it a draw. if len(self.sessionplayers) > 1 and len(winners) < 2: self.setactivity( - _ba.newactivity(DrawScoreScreenActivity, {'results': results})) + _ba.newactivity(DrawScoreScreenActivity, {'results': results}) + ) else: # Award different point amounts based on number of players. point_awards = self.get_ffa_point_awards() for i, winner in enumerate(winners): for team in winner.teams: - points = (point_awards[i] if i in point_awards else 0) - team.customdata['previous_score'] = ( - team.customdata['score']) + points = point_awards[i] if i in point_awards else 0 + team.customdata['previous_score'] = team.customdata['score'] team.customdata['score'] += points series_winners = [ - team for team in self.sessionteams + team + for team in self.sessionteams if team.customdata['score'] >= self._ffa_series_length ] series_winners.sort( reverse=True, - key=lambda t: asserttype(t.customdata['score'], int)) - if (len(series_winners) == 1 - or (len(series_winners) > 1 - and series_winners[0].customdata['score'] != - series_winners[1].customdata['score'])): + key=lambda t: asserttype(t.customdata['score'], int), + ) + if len(series_winners) == 1 or ( + len(series_winners) > 1 + and series_winners[0].customdata['score'] + != series_winners[1].customdata['score'] + ): self.setactivity( - _ba.newactivity(TeamSeriesVictoryScoreScreenActivity, - {'winner': series_winners[0]})) + _ba.newactivity( + TeamSeriesVictoryScoreScreenActivity, + {'winner': series_winners[0]}, + ) + ) else: self.setactivity( - _ba.newactivity(FreeForAllVictoryScoreScreenActivity, - {'results': results})) + _ba.newactivity( + FreeForAllVictoryScoreScreenActivity, + {'results': results}, + ) + ) diff --git a/dist/ba_data/python/ba/_gameactivity.py b/dist/ba_data/python/ba/_gameactivity.py index 46dcdf0..697c14a 100644 --- a/dist/ba_data/python/ba/_gameactivity.py +++ b/dist/ba_data/python/ba/_gameactivity.py @@ -37,6 +37,7 @@ class GameActivity(Activity[PlayerType, TeamType]): Category: **Gameplay Classes** """ + # pylint: disable=too-many-public-methods # Tips to be presented to the user at the start of the game. @@ -89,14 +90,14 @@ class GameActivity(Activity[PlayerType, TeamType]): """ delegate = _ba.app.delegate assert delegate is not None - delegate.create_default_game_settings_ui(cls, sessiontype, settings, - completion_call) + delegate.create_default_game_settings_ui( + cls, sessiontype, settings, completion_call + ) @classmethod def getscoreconfig(cls) -> ba.ScoreConfig: """Return info about game scoring setup; can be overridden by games.""" - return (cls.scoreconfig - if cls.scoreconfig is not None else ScoreConfig()) + return cls.scoreconfig if cls.scoreconfig is not None else ScoreConfig() @classmethod def getname(cls) -> str: @@ -119,11 +120,13 @@ class GameActivity(Activity[PlayerType, TeamType]): # their own and should not rely on hard-coded settings names. if settings is not None: if 'Solo Mode' in settings and settings['Solo Mode']: - name = Lstr(resource='soloNameFilterText', - subs=[('${NAME}', name)]) + name = Lstr( + resource='soloNameFilterText', subs=[('${NAME}', name)] + ) if 'Epic Mode' in settings and settings['Epic Mode']: - name = Lstr(resource='epicNameFilterText', - subs=[('${NAME}', name)]) + name = Lstr( + resource='epicNameFilterText', subs=[('${NAME}', name)] + ) return name @@ -145,7 +148,8 @@ class GameActivity(Activity[PlayerType, TeamType]): @classmethod def get_description_display_string( - cls, sessiontype: type[ba.Session]) -> ba.Lstr: + cls, sessiontype: type[ba.Session] + ) -> ba.Lstr: """Return a translated version of get_description(). Sub-classes should override get_description(); not this. @@ -155,7 +159,8 @@ class GameActivity(Activity[PlayerType, TeamType]): @classmethod def get_available_settings( - cls, sessiontype: type[ba.Session]) -> list[ba.Setting]: + cls, sessiontype: type[ba.Session] + ) -> list[ba.Setting]: """Return a list of settings relevant to this game type when running under the provided session type. """ @@ -184,19 +189,33 @@ class GameActivity(Activity[PlayerType, TeamType]): # In newer configs, map is in settings; it used to be in the # config root. if 'map' in config['settings']: - sval = Lstr(value='${NAME} @ ${MAP}', - subs=[('${NAME}', name), - ('${MAP}', - _map.get_map_display_string( - _map.get_filtered_map_name( - config['settings']['map'])))]) + sval = Lstr( + value='${NAME} @ ${MAP}', + subs=[ + ('${NAME}', name), + ( + '${MAP}', + _map.get_map_display_string( + _map.get_filtered_map_name( + config['settings']['map'] + ) + ), + ), + ], + ) elif 'map' in config: - sval = Lstr(value='${NAME} @ ${MAP}', - subs=[('${NAME}', name), - ('${MAP}', - _map.get_map_display_string( - _map.get_filtered_map_name(config['map']))) - ]) + sval = Lstr( + value='${NAME} @ ${MAP}', + subs=[ + ('${NAME}', name), + ( + '${MAP}', + _map.get_map_display_string( + _map.get_filtered_map_name(config['map']) + ), + ), + ], + ) else: print('invalid game config - expected map entry under settings') sval = Lstr(value='???') @@ -242,11 +261,14 @@ class GameActivity(Activity[PlayerType, TeamType]): self._is_waiting_for_continue = False self._continue_cost = _internal.get_v1_account_misc_read_val( - 'continueStartCost', 25) + 'continueStartCost', 25 + ) self._continue_cost_mult = _internal.get_v1_account_misc_read_val( - 'continuesMult', 2) + 'continuesMult', 2 + ) self._continue_cost_offset = _internal.get_v1_account_misc_read_val( - 'continuesOffset', 0) + 'continuesOffset', 0 + ) @property def map(self) -> ba.Map: @@ -273,11 +295,13 @@ class GameActivity(Activity[PlayerType, TeamType]): # FIXME: Should clean this up. try: from ba._coopsession import CoopSession + if isinstance(self.session, CoopSession): campaign = self.session.campaign assert campaign is not None return campaign.getlevel( - self.session.campaign_level_name).displayname + self.session.campaign_level_name + ).displayname except Exception: print_error('error getting campaign level name') return self.get_instance_display_string() @@ -348,6 +372,7 @@ class GameActivity(Activity[PlayerType, TeamType]): if music is not None: from ba import _music + _music.setmusic(music) def on_continue(self) -> None: @@ -365,14 +390,14 @@ class GameActivity(Activity[PlayerType, TeamType]): if do_continue: _ba.playsound(_ba.getsound('shieldUp')) _ba.playsound(_ba.getsound('cashRegister')) - _internal.add_transaction({ - 'type': 'CONTINUE', - 'cost': self._continue_cost - }) + _internal.add_transaction( + {'type': 'CONTINUE', 'cost': self._continue_cost} + ) _internal.run_transactions() self._continue_cost = ( - self._continue_cost * self._continue_cost_mult + - self._continue_cost_offset) + self._continue_cost * self._continue_cost_mult + + self._continue_cost_offset + ) self.on_continue() else: self.end_game() @@ -392,8 +417,7 @@ class GameActivity(Activity[PlayerType, TeamType]): from ba._generated.enums import TimeType try: - if _internal.get_v1_account_misc_read_val('enableContinues', - False): + if _internal.get_v1_account_misc_read_val('enableContinues', False): session = self.session # We only support continuing in non-tournament games. @@ -409,8 +433,10 @@ class GameActivity(Activity[PlayerType, TeamType]): # Only attempt this if we're not currently paused # and there appears to be no UI. - if (not gnode.paused - and not _ba.app.ui.has_main_menu_window()): + if ( + not gnode.paused + and not _ba.app.ui.has_main_menu_window() + ): self._is_waiting_for_continue = True with _ba.Context('ui'): _ba.timer( @@ -419,10 +445,14 @@ class GameActivity(Activity[PlayerType, TeamType]): self, self._continue_cost, continue_call=WeakCall( - self._continue_choice, True), + self._continue_choice, True + ), cancel_call=WeakCall( - self._continue_choice, False)), - timetype=TimeType.REAL) + self._continue_choice, False + ), + ), + timetype=TimeType.REAL, + ) return except Exception: @@ -432,6 +462,7 @@ class GameActivity(Activity[PlayerType, TeamType]): def on_begin(self) -> None: from ba._analytics import game_begin_analytics + super().on_begin() game_begin_analytics() @@ -459,20 +490,22 @@ class GameActivity(Activity[PlayerType, TeamType]): _internal.tournament_query( args={ 'tournamentIDs': [tournament_id], - 'source': 'in-game time remaining query' + 'source': 'in-game time remaining query', }, callback=WeakCall(self._on_tournament_query_response), ) - def _on_tournament_query_response(self, - data: dict[str, Any] | None) -> None: + def _on_tournament_query_response( + self, data: dict[str, Any] | None + ) -> None: if data is not None: data_t = data['t'] # This used to be the whole payload. # Keep our cached tourney info up to date _ba.app.accounts_v1.cache_tournament_info(data_t) self._setup_tournament_time_limit( - max(5, data_t[0]['timeRemaining'])) + max(5, data_t[0]['timeRemaining']) + ) def on_player_join(self, player: PlayerType) -> None: super().on_player_join(player) @@ -489,9 +522,9 @@ class GameActivity(Activity[PlayerType, TeamType]): killer = msg.getkillerplayer(self.playertype) # Inform our stats of the demise. - self.stats.player_was_killed(player, - killed=msg.killed, - killer=killer) + self.stats.player_was_killed( + player, killed=msg.killed, killer=killer + ) # Award the killer points if he's on a different team. # FIXME: This should not be linked to Spaz actors. @@ -500,12 +533,14 @@ class GameActivity(Activity[PlayerType, TeamType]): assert isinstance(killer.actor, Spaz) pts, importance = killer.actor.get_death_points(msg.how) if not self.has_ended(): - self.stats.player_scored(killer, - pts, - kill=True, - victim_player=player, - importance=importance, - showpoints=self.show_kill_points) + self.stats.player_scored( + killer, + pts, + kill=True, + victim_player=player, + importance=importance, + showpoints=self.show_kill_points, + ) else: return super().handlemessage(msg) return None @@ -520,6 +555,7 @@ class GameActivity(Activity[PlayerType, TeamType]): from ba._freeforallsession import FreeForAllSession from ba._gameutils import animate from ba._nodeactor import NodeActor + sb_name = self.get_instance_scoreboard_display_string() # The description can be either a string or a sequence with args @@ -533,43 +569,51 @@ class GameActivity(Activity[PlayerType, TeamType]): if not isinstance(sb_desc_l[0], str): raise TypeError('Invalid format for instance description.') - is_empty = (sb_desc_l[0] == '') + is_empty = sb_desc_l[0] == '' subs = [] for i in range(len(sb_desc_l) - 1): subs.append(('${ARG' + str(i + 1) + '}', str(sb_desc_l[i + 1]))) - translation = Lstr(translate=('gameDescriptions', sb_desc_l[0]), - subs=subs) + translation = Lstr( + translate=('gameDescriptions', sb_desc_l[0]), subs=subs + ) sb_desc = translation vrmode = _ba.app.vr_mode yval = -34 if is_empty else -20 yval -= 16 - sbpos = ((15, yval) if isinstance(self.session, FreeForAllSession) else - (15, yval)) + sbpos = ( + (15, yval) + if isinstance(self.session, FreeForAllSession) + else (15, yval) + ) self._game_scoreboard_name_text = NodeActor( - _ba.newnode('text', - attrs={ - 'text': sb_name, - 'maxwidth': 300, - 'position': sbpos, - 'h_attach': 'left', - 'vr_depth': 10, - 'v_attach': 'top', - 'v_align': 'bottom', - 'color': (1.0, 1.0, 1.0, 1.0), - 'shadow': 1.0 if vrmode else 0.6, - 'flatness': 1.0 if vrmode else 0.5, - 'scale': 1.1 - })) + _ba.newnode( + 'text', + attrs={ + 'text': sb_name, + 'maxwidth': 300, + 'position': sbpos, + 'h_attach': 'left', + 'vr_depth': 10, + 'v_attach': 'top', + 'v_align': 'bottom', + 'color': (1.0, 1.0, 1.0, 1.0), + 'shadow': 1.0 if vrmode else 0.6, + 'flatness': 1.0 if vrmode else 0.5, + 'scale': 1.1, + }, + ) + ) assert self._game_scoreboard_name_text.node - animate(self._game_scoreboard_name_text.node, 'opacity', { - 0: 0.0, - 1.0: 1.0 - }) + animate( + self._game_scoreboard_name_text.node, 'opacity', {0: 0.0, 1.0: 1.0} + ) - descpos = (((17, -44 + - 10) if isinstance(self.session, FreeForAllSession) else - (17, -44 + 10))) + descpos = ( + (17, -44 + 10) + if isinstance(self.session, FreeForAllSession) + else (17, -44 + 10) + ) self._game_scoreboard_description_text = NodeActor( _ba.newnode( 'text', @@ -583,28 +627,34 @@ class GameActivity(Activity[PlayerType, TeamType]): 'v_align': 'top', 'shadow': 1.0 if vrmode else 0.7, 'flatness': 1.0 if vrmode else 0.8, - 'color': (1, 1, 1, 1) if vrmode else (0.9, 0.9, 0.9, 1.0) - })) + 'color': (1, 1, 1, 1) if vrmode else (0.9, 0.9, 0.9, 1.0), + }, + ) + ) assert self._game_scoreboard_description_text.node - animate(self._game_scoreboard_description_text.node, 'opacity', { - 0: 0.0, - 1.0: 1.0 - }) + animate( + self._game_scoreboard_description_text.node, + 'opacity', + {0: 0.0, 1.0: 1.0}, + ) def _show_info(self) -> None: """Show the game description.""" from ba._gameutils import animate from bastd.actor.zoomtext import ZoomText + name = self.get_instance_display_string() - ZoomText(name, - maxwidth=800, - lifespan=2.5, - jitter=2.0, - position=(0, 180), - flash=False, - color=(0.93 * 1.25, 0.9 * 1.25, 1.0 * 1.25), - trailcolor=(0.15, 0.05, 1.0, 0.0)).autoretain() + ZoomText( + name, + maxwidth=800, + lifespan=2.5, + jitter=2.0, + position=(0, 180), + flash=False, + color=(0.93 * 1.25, 0.9 * 1.25, 1.0 * 1.25), + trailcolor=(0.15, 0.05, 1.0, 0.0), + ).autoretain() _ba.timer(0.2, Call(_ba.playsound, _ba.getsound('gong'))) # The description can be either a string or a sequence with args @@ -620,36 +670,36 @@ class GameActivity(Activity[PlayerType, TeamType]): subs = [] for i in range(len(desc_l) - 1): subs.append(('${ARG' + str(i + 1) + '}', str(desc_l[i + 1]))) - translation = Lstr(translate=('gameDescriptions', desc_l[0]), - subs=subs) + translation = Lstr(translate=('gameDescriptions', desc_l[0]), subs=subs) # Do some standard filters (epic mode, etc). if self.settings_raw.get('Epic Mode', False): - translation = Lstr(resource='epicDescriptionFilterText', - subs=[('${DESCRIPTION}', translation)]) + translation = Lstr( + resource='epicDescriptionFilterText', + subs=[('${DESCRIPTION}', translation)], + ) vrmode = _ba.app.vr_mode - dnode = _ba.newnode('text', - attrs={ - 'v_attach': 'center', - 'h_attach': 'center', - 'h_align': 'center', - 'color': (1, 1, 1, 1), - 'shadow': 1.0 if vrmode else 0.5, - 'flatness': 1.0 if vrmode else 0.5, - 'vr_depth': -30, - 'position': (0, 80), - 'scale': 1.2, - 'maxwidth': 700, - 'text': translation - }) - cnode = _ba.newnode('combine', - owner=dnode, - attrs={ - 'input0': 1.0, - 'input1': 1.0, - 'input2': 1.0, - 'size': 4 - }) + dnode = _ba.newnode( + 'text', + attrs={ + 'v_attach': 'center', + 'h_attach': 'center', + 'h_align': 'center', + 'color': (1, 1, 1, 1), + 'shadow': 1.0 if vrmode else 0.5, + 'flatness': 1.0 if vrmode else 0.5, + 'vr_depth': -30, + 'position': (0, 80), + 'scale': 1.2, + 'maxwidth': 700, + 'text': translation, + }, + ) + cnode = _ba.newnode( + 'combine', + owner=dnode, + attrs={'input0': 1.0, 'input1': 1.0, 'input2': 1.0, 'size': 4}, + ) cnode.connectattr('output', dnode, 'color') keys = {0.5: 0, 1.0: 1.0, 2.5: 1.0, 4.0: 0.0} animate(cnode, 'input3', keys) @@ -663,8 +713,9 @@ class GameActivity(Activity[PlayerType, TeamType]): # If there's any tips left on the list, display one. if self.tips: tip = self.tips.pop(random.randrange(len(self.tips))) - tip_title = Lstr(value='${A}:', - subs=[('${A}', Lstr(resource='tipText'))]) + tip_title = Lstr( + value='${A}:', subs=[('${A}', Lstr(resource='tipText'))] + ) icon: ba.Texture | None = None sound: ba.Sound | None = None if isinstance(tip, GameTip): @@ -674,80 +725,84 @@ class GameActivity(Activity[PlayerType, TeamType]): assert isinstance(tip, str) # Do a few substitutions. - tip_lstr = Lstr(translate=('tips', tip), - subs=[('${PICKUP}', - _ba.charstr(SpecialChar.TOP_BUTTON))]) + tip_lstr = Lstr( + translate=('tips', tip), + subs=[('${PICKUP}', _ba.charstr(SpecialChar.TOP_BUTTON))], + ) base_position = (75, 50) tip_scale = 0.8 tip_title_scale = 1.2 vrmode = _ba.app.vr_mode t_offs = -350.0 - tnode = _ba.newnode('text', - attrs={ - 'text': tip_lstr, - 'scale': tip_scale, - 'maxwidth': 900, - 'position': (base_position[0] + t_offs, - base_position[1]), - 'h_align': 'left', - 'vr_depth': 300, - 'shadow': 1.0 if vrmode else 0.5, - 'flatness': 1.0 if vrmode else 0.5, - 'v_align': 'center', - 'v_attach': 'bottom' - }) - t2pos = (base_position[0] + t_offs - (20 if icon is None else 82), - base_position[1] + 2) - t2node = _ba.newnode('text', - owner=tnode, - attrs={ - 'text': tip_title, - 'scale': tip_title_scale, - 'position': t2pos, - 'h_align': 'right', - 'vr_depth': 300, - 'shadow': 1.0 if vrmode else 0.5, - 'flatness': 1.0 if vrmode else 0.5, - 'maxwidth': 140, - 'v_align': 'center', - 'v_attach': 'bottom' - }) + tnode = _ba.newnode( + 'text', + attrs={ + 'text': tip_lstr, + 'scale': tip_scale, + 'maxwidth': 900, + 'position': (base_position[0] + t_offs, base_position[1]), + 'h_align': 'left', + 'vr_depth': 300, + 'shadow': 1.0 if vrmode else 0.5, + 'flatness': 1.0 if vrmode else 0.5, + 'v_align': 'center', + 'v_attach': 'bottom', + }, + ) + t2pos = ( + base_position[0] + t_offs - (20 if icon is None else 82), + base_position[1] + 2, + ) + t2node = _ba.newnode( + 'text', + owner=tnode, + attrs={ + 'text': tip_title, + 'scale': tip_title_scale, + 'position': t2pos, + 'h_align': 'right', + 'vr_depth': 300, + 'shadow': 1.0 if vrmode else 0.5, + 'flatness': 1.0 if vrmode else 0.5, + 'maxwidth': 140, + 'v_align': 'center', + 'v_attach': 'bottom', + }, + ) if icon is not None: ipos = (base_position[0] + t_offs - 40, base_position[1] + 1) - img = _ba.newnode('image', - attrs={ - 'texture': icon, - 'position': ipos, - 'scale': (50, 50), - 'opacity': 1.0, - 'vr_depth': 315, - 'color': (1, 1, 1), - 'absolute_scale': True, - 'attach': 'bottomCenter' - }) + img = _ba.newnode( + 'image', + attrs={ + 'texture': icon, + 'position': ipos, + 'scale': (50, 50), + 'opacity': 1.0, + 'vr_depth': 315, + 'color': (1, 1, 1), + 'absolute_scale': True, + 'attach': 'bottomCenter', + }, + ) animate(img, 'opacity', {0: 0, 1.0: 1, 4.0: 1, 5.0: 0}) _ba.timer(5.0, img.delete) if sound is not None: _ba.playsound(sound) - combine = _ba.newnode('combine', - owner=tnode, - attrs={ - 'input0': 1.0, - 'input1': 0.8, - 'input2': 1.0, - 'size': 4 - }) + combine = _ba.newnode( + 'combine', + owner=tnode, + attrs={'input0': 1.0, 'input1': 0.8, 'input2': 1.0, 'size': 4}, + ) combine.connectattr('output', tnode, 'color') combine.connectattr('output', t2node, 'color') animate(combine, 'input3', {0: 0, 1.0: 1, 4.0: 1, 5.0: 0}) _ba.timer(5.0, tnode.delete) - def end(self, - results: Any = None, - delay: float = 0.0, - force: bool = False) -> None: + def end( + self, results: Any = None, delay: float = 0.0, force: bool = False + ) -> None: from ba._gameresults import GameResults # If results is a standard team-game-results, associate it with us @@ -757,14 +812,18 @@ class GameActivity(Activity[PlayerType, TeamType]): # If we had a standard time-limit that had not expired, stop it so # it doesnt tick annoyingly. - if (self._standard_time_limit_time is not None - and self._standard_time_limit_time > 0): + if ( + self._standard_time_limit_time is not None + and self._standard_time_limit_time > 0 + ): self._standard_time_limit_timer = None self._standard_time_limit_text = None # Ditto with tournament time limits. - if (self._tournament_time_limit is not None - and self._tournament_time_limit > 0): + if ( + self._tournament_time_limit is not None + and self._tournament_time_limit > 0 + ): self._tournament_time_limit_timer = None self._tournament_time_limit_text = None self._tournament_time_limit_title_text = None @@ -779,12 +838,14 @@ class GameActivity(Activity[PlayerType, TeamType]): yet; this way things like the standard time-limit (ba.GameActivity.setup_standard_time_limit()) will work with the game. """ - print('WARNING: default end_game() implementation called;' - ' your game should override this.') + print( + 'WARNING: default end_game() implementation called;' + ' your game should override this.' + ) - def respawn_player(self, - player: PlayerType, - respawn_time: float | None = None) -> None: + def respawn_player( + self, player: PlayerType, respawn_time: float | None = None + ) -> None: """ Given a ba.Player, sets up a standard respawn timer, along with the standard counter display, etc. @@ -817,10 +878,13 @@ class GameActivity(Activity[PlayerType, TeamType]): if player.actor and not self.has_ended(): from bastd.actor.respawnicon import RespawnIcon + player.customdata['respawn_timer'] = _ba.Timer( - respawn_time, WeakCall(self.spawn_player_if_exists, player)) + respawn_time, WeakCall(self.spawn_player_if_exists, player) + ) player.customdata['respawn_icon'] = RespawnIcon( - player, respawn_time) + player, respawn_time + ) def spawn_player_if_exists(self, player: PlayerType) -> None: """ @@ -841,10 +905,12 @@ class GameActivity(Activity[PlayerType, TeamType]): return self.spawn_player_spaz(player) - def spawn_player_spaz(self, - player: PlayerType, - position: Sequence[float] = (0, 0, 0), - angle: float | None = None) -> PlayerSpaz: + def spawn_player_spaz( + self, + player: PlayerType, + position: Sequence[float] = (0, 0, 0), + angle: float | None = None, + ) -> PlayerSpaz: """Create and wire up a ba.PlayerSpaz for the provided ba.Player.""" # pylint: disable=too-many-locals # pylint: disable=cyclic-import @@ -852,6 +918,7 @@ class GameActivity(Activity[PlayerType, TeamType]): from ba._gameutils import animate from ba._coopsession import CoopSession from bastd.actor.playerspaz import PlayerSpaz + name = player.getname() color = player.color highlight = player.highlight @@ -862,10 +929,12 @@ class GameActivity(Activity[PlayerType, TeamType]): light_color = _math.normalized_color(color) display_color = _ba.safecolor(color, target_intensity=0.75) - spaz = playerspaztype(color=color, - highlight=highlight, - character=player.character, - player=player) + spaz = playerspaztype( + color=color, + highlight=highlight, + character=player.character, + player=player, + ) player.actor = spaz assert spaz.node @@ -874,13 +943,14 @@ class GameActivity(Activity[PlayerType, TeamType]): # material that allows us to collide with the player-walls. # FIXME: Need to generalize this. if isinstance(self.session, CoopSession) and self.map.getname() in [ - 'Courtyard', 'Tower D' + 'Courtyard', + 'Tower D', ]: mat = self.map.preloaddata['collide_with_wall_material'] assert isinstance(spaz.node.materials, tuple) assert isinstance(spaz.node.roller_materials, tuple) - spaz.node.materials += (mat, ) - spaz.node.roller_materials += (mat, ) + spaz.node.materials += (mat,) + spaz.node.roller_materials += (mat,) spaz.node.name = name spaz.node.name_color = display_color @@ -889,8 +959,9 @@ class GameActivity(Activity[PlayerType, TeamType]): # Move to the stand position and add a flash of light. spaz.handlemessage( StandMessage( - position, - angle if angle is not None else random.uniform(0, 360))) + position, angle if angle is not None else random.uniform(0, 360) + ) + ) _ba.playsound(self._spawn_sound, 1, position=spaz.node.position) light = _ba.newnode('light', attrs={'color': light_color}) spaz.node.connectattr('position', light, 'position') @@ -902,10 +973,12 @@ class GameActivity(Activity[PlayerType, TeamType]): """Create standard powerup drops for the current map.""" # pylint: disable=cyclic-import from bastd.actor.powerupbox import DEFAULT_POWERUP_INTERVAL - self._powerup_drop_timer = _ba.Timer(DEFAULT_POWERUP_INTERVAL, - WeakCall( - self._standard_drop_powerups), - repeat=True) + + self._powerup_drop_timer = _ba.Timer( + DEFAULT_POWERUP_INTERVAL, + WeakCall(self._standard_drop_powerups), + repeat=True, + ) self._standard_drop_powerups() if enable_tnt: self._tnt_spawners = {} @@ -914,10 +987,12 @@ class GameActivity(Activity[PlayerType, TeamType]): def _standard_drop_powerup(self, index: int, expire: bool = True) -> None: # pylint: disable=cyclic-import from bastd.actor.powerupbox import PowerupBox, PowerupBoxFactory + PowerupBox( position=self.map.powerup_spawn_points[index], poweruptype=PowerupBoxFactory.get().get_random_powerup_type(), - expire=expire).autoretain() + expire=expire, + ).autoretain() def _standard_drop_powerups(self) -> None: """Standard powerup drop.""" @@ -931,6 +1006,7 @@ class GameActivity(Activity[PlayerType, TeamType]): """Standard tnt drop.""" # pylint: disable=cyclic-import from bastd.actor.bomb import TNTSpawner + for i, point in enumerate(self.map.tnt_points): assert self._tnt_spawners is not None if self._tnt_spawners.get(i) is None: @@ -944,38 +1020,44 @@ class GameActivity(Activity[PlayerType, TeamType]): If the time-limit expires, end_game() will be called. """ from ba._nodeactor import NodeActor + if duration <= 0.0: return self._standard_time_limit_time = int(duration) self._standard_time_limit_timer = _ba.Timer( - 1.0, WeakCall(self._standard_time_limit_tick), repeat=True) + 1.0, WeakCall(self._standard_time_limit_tick), repeat=True + ) self._standard_time_limit_text = NodeActor( - _ba.newnode('text', - attrs={ - 'v_attach': 'top', - 'h_attach': 'center', - 'h_align': 'left', - 'color': (1.0, 1.0, 1.0, 0.5), - 'position': (-25, -30), - 'flatness': 1.0, - 'scale': 0.9 - })) + _ba.newnode( + 'text', + attrs={ + 'v_attach': 'top', + 'h_attach': 'center', + 'h_align': 'left', + 'color': (1.0, 1.0, 1.0, 0.5), + 'position': (-25, -30), + 'flatness': 1.0, + 'scale': 0.9, + }, + ) + ) self._standard_time_limit_text_input = NodeActor( - _ba.newnode('timedisplay', - attrs={ - 'time2': duration * 1000, - 'timemin': 0 - })) - self.globalsnode.connectattr('time', - self._standard_time_limit_text_input.node, - 'time1') + _ba.newnode( + 'timedisplay', attrs={'time2': duration * 1000, 'timemin': 0} + ) + ) + self.globalsnode.connectattr( + 'time', self._standard_time_limit_text_input.node, 'time1' + ) assert self._standard_time_limit_text_input.node assert self._standard_time_limit_text.node self._standard_time_limit_text_input.node.connectattr( - 'output', self._standard_time_limit_text.node, 'text') + 'output', self._standard_time_limit_text.node, 'text' + ) def _standard_time_limit_tick(self) -> None: from ba._gameutils import animate + assert self._standard_time_limit_time is not None self._standard_time_limit_time -= 1 if self._standard_time_limit_time <= 10: @@ -984,11 +1066,14 @@ class GameActivity(Activity[PlayerType, TeamType]): assert self._standard_time_limit_text.node self._standard_time_limit_text.node.scale = 1.3 self._standard_time_limit_text.node.position = (-30, -45) - cnode = _ba.newnode('combine', - owner=self._standard_time_limit_text.node, - attrs={'size': 4}) - cnode.connectattr('output', - self._standard_time_limit_text.node, 'color') + cnode = _ba.newnode( + 'combine', + owner=self._standard_time_limit_text.node, + attrs={'size': 4}, + ) + cnode.connectattr( + 'output', self._standard_time_limit_text.node, 'color' + ) animate(cnode, 'input0', {0: 1, 0.15: 1}, loop=True) animate(cnode, 'input1', {0: 1, 0.15: 0.5}, loop=True) animate(cnode, 'input2', {0: 0.1, 0.15: 0.0}, loop=True) @@ -997,16 +1082,18 @@ class GameActivity(Activity[PlayerType, TeamType]): if self._standard_time_limit_time <= 0: self._standard_time_limit_timer = None self.end_game() - node = _ba.newnode('text', - attrs={ - 'v_attach': 'top', - 'h_attach': 'center', - 'h_align': 'center', - 'color': (1, 0.7, 0, 1), - 'position': (0, -90), - 'scale': 1.2, - 'text': Lstr(resource='timeExpiredText') - }) + node = _ba.newnode( + 'text', + attrs={ + 'v_attach': 'top', + 'h_attach': 'center', + 'h_align': 'center', + 'color': (1, 0.7, 0, 1), + 'position': (0, -90), + 'scale': 1.2, + 'text': Lstr(resource='timeExpiredText'), + }, + ) _ba.playsound(_ba.getsound('refWhistle')) animate(node, 'scale', {0.0: 0.0, 0.1: 1.4, 0.15: 1.2}) @@ -1019,6 +1106,7 @@ class GameActivity(Activity[PlayerType, TeamType]): """ from ba._nodeactor import NodeActor from ba._generated.enums import TimeType + if duration <= 0.0: return self._tournament_time_limit = int(duration) @@ -1031,49 +1119,61 @@ class GameActivity(Activity[PlayerType, TeamType]): 1.0, WeakCall(self._tournament_time_limit_tick), repeat=True, - timetype=TimeType.BASE) + timetype=TimeType.BASE, + ) self._tournament_time_limit_title_text = NodeActor( - _ba.newnode('text', - attrs={ - 'v_attach': 'bottom', - 'h_attach': 'left', - 'h_align': 'center', - 'v_align': 'center', - 'vr_depth': 300, - 'maxwidth': 100, - 'color': (1.0, 1.0, 1.0, 0.5), - 'position': (60, 50), - 'flatness': 1.0, - 'scale': 0.5, - 'text': Lstr(resource='tournamentText') - })) + _ba.newnode( + 'text', + attrs={ + 'v_attach': 'bottom', + 'h_attach': 'left', + 'h_align': 'center', + 'v_align': 'center', + 'vr_depth': 300, + 'maxwidth': 100, + 'color': (1.0, 1.0, 1.0, 0.5), + 'position': (60, 50), + 'flatness': 1.0, + 'scale': 0.5, + 'text': Lstr(resource='tournamentText'), + }, + ) + ) self._tournament_time_limit_text = NodeActor( - _ba.newnode('text', - attrs={ - 'v_attach': 'bottom', - 'h_attach': 'left', - 'h_align': 'center', - 'v_align': 'center', - 'vr_depth': 300, - 'maxwidth': 100, - 'color': (1.0, 1.0, 1.0, 0.5), - 'position': (60, 30), - 'flatness': 1.0, - 'scale': 0.9 - })) + _ba.newnode( + 'text', + attrs={ + 'v_attach': 'bottom', + 'h_attach': 'left', + 'h_align': 'center', + 'v_align': 'center', + 'vr_depth': 300, + 'maxwidth': 100, + 'color': (1.0, 1.0, 1.0, 0.5), + 'position': (60, 30), + 'flatness': 1.0, + 'scale': 0.9, + }, + ) + ) self._tournament_time_limit_text_input = NodeActor( - _ba.newnode('timedisplay', - attrs={ - 'timemin': 0, - 'time2': self._tournament_time_limit * 1000 - })) + _ba.newnode( + 'timedisplay', + attrs={ + 'timemin': 0, + 'time2': self._tournament_time_limit * 1000, + }, + ) + ) assert self._tournament_time_limit_text.node assert self._tournament_time_limit_text_input.node self._tournament_time_limit_text_input.node.connectattr( - 'output', self._tournament_time_limit_text.node, 'text') + 'output', self._tournament_time_limit_text.node, 'text' + ) def _tournament_time_limit_tick(self) -> None: from ba._gameutils import animate + assert self._tournament_time_limit is not None self._tournament_time_limit -= 1 if self._tournament_time_limit <= 10: @@ -1089,13 +1189,16 @@ class GameActivity(Activity[PlayerType, TeamType]): cnode = _ba.newnode( 'combine', owner=self._tournament_time_limit_text.node, - attrs={'size': 4}) - cnode.connectattr('output', - self._tournament_time_limit_title_text.node, - 'color') - cnode.connectattr('output', - self._tournament_time_limit_text.node, - 'color') + attrs={'size': 4}, + ) + cnode.connectattr( + 'output', + self._tournament_time_limit_title_text.node, + 'color', + ) + cnode.connectattr( + 'output', self._tournament_time_limit_text.node, 'color' + ) animate(cnode, 'input0', {0: 1, 0.15: 1}, loop=True) animate(cnode, 'input1', {0: 1, 0.15: 0.5}, loop=True) animate(cnode, 'input2', {0: 0.1, 0.15: 0.0}, loop=True) @@ -1104,18 +1207,22 @@ class GameActivity(Activity[PlayerType, TeamType]): if self._tournament_time_limit <= 0: self._tournament_time_limit_timer = None self.end_game() - tval = Lstr(resource='tournamentTimeExpiredText', - fallback_resource='timeExpiredText') - node = _ba.newnode('text', - attrs={ - 'v_attach': 'top', - 'h_attach': 'center', - 'h_align': 'center', - 'color': (1, 0.7, 0, 1), - 'position': (0, -200), - 'scale': 1.6, - 'text': tval - }) + tval = Lstr( + resource='tournamentTimeExpiredText', + fallback_resource='timeExpiredText', + ) + node = _ba.newnode( + 'text', + attrs={ + 'v_attach': 'top', + 'h_attach': 'center', + 'h_align': 'center', + 'color': (1, 0.7, 0, 1), + 'position': (0, -200), + 'scale': 1.6, + 'text': tval, + }, + ) _ba.playsound(_ba.getsound('refWhistle')) animate(node, 'scale', {0: 0.0, 0.1: 1.4, 0.15: 1.2}) @@ -1124,14 +1231,17 @@ class GameActivity(Activity[PlayerType, TeamType]): assert self._tournament_time_limit_text_input is not None assert self._tournament_time_limit_text_input.node self._tournament_time_limit_text_input.node.time2 = ( - self._tournament_time_limit * 1000) + self._tournament_time_limit * 1000 + ) - def show_zoom_message(self, - message: ba.Lstr, - color: Sequence[float] = (0.9, 0.4, 0.0), - scale: float = 0.8, - duration: float = 2.0, - trail: bool = False) -> None: + def show_zoom_message( + self, + message: ba.Lstr, + color: Sequence[float] = (0.9, 0.4, 0.0), + scale: float = 0.8, + duration: float = 2.0, + trail: bool = False, + ) -> None: """Zooming text used to announce game names and winners.""" # pylint: disable=cyclic-import from bastd.actor.zoomtext import ZoomText @@ -1141,19 +1251,23 @@ class GameActivity(Activity[PlayerType, TeamType]): i = 0 cur_time = _ba.time() while True: - if (i not in self._zoom_message_times - or self._zoom_message_times[i] < cur_time): + if ( + i not in self._zoom_message_times + or self._zoom_message_times[i] < cur_time + ): self._zoom_message_times[i] = cur_time + duration break i += 1 - ZoomText(message, - lifespan=duration, - jitter=2.0, - position=(0, 200 - i * 100), - scale=scale, - maxwidth=800, - trail=trail, - color=color).autoretain() + ZoomText( + message, + lifespan=duration, + jitter=2.0, + position=(0, 200 - i * 100), + scale=scale, + maxwidth=800, + trail=trail, + color=color, + ).autoretain() def _calc_map_name(self, settings: dict) -> str: map_name: str @@ -1164,7 +1278,8 @@ class GameActivity(Activity[PlayerType, TeamType]): # list of supported ones. unowned_maps = _store.get_unowned_maps() valid_maps: list[str] = [ - m for m in self.get_supported_maps(type(self.session)) + m + for m in self.get_supported_maps(type(self.session)) if m not in unowned_maps ] if not valid_maps: diff --git a/dist/ba_data/python/ba/_gameresults.py b/dist/ba_data/python/ba/_gameresults.py index d580e86..7f22315 100644 --- a/dist/ba_data/python/ba/_gameresults.py +++ b/dist/ba_data/python/ba/_gameresults.py @@ -19,6 +19,7 @@ if TYPE_CHECKING: @dataclass class WinnerGroup: """Entry for a winning team or teams calculated by game-results.""" + score: int | None teams: Sequence[ba.SessionTeam] @@ -35,8 +36,9 @@ class GameResults: def __init__(self) -> None: self._game_set = False - self._scores: dict[int, tuple[weakref.ref[ba.SessionTeam], - int | None]] = {} + self._scores: dict[ + int, tuple[weakref.ref[ba.SessionTeam], int | None] + ] = {} self._sessionteams: list[weakref.ref[ba.SessionTeam]] | None = None self._playerinfos: list[ba.PlayerInfo] | None = None self._lower_is_better: bool | None = None @@ -96,8 +98,7 @@ class GameResults: """Return whether there is a score for a given session-team.""" return any(s[0]() is sessionteam for s in self._scores.values()) - def get_sessionteam_score_str(self, - sessionteam: ba.SessionTeam) -> ba.Lstr: + def get_sessionteam_score_str(self, sessionteam: ba.SessionTeam) -> ba.Lstr: """Return the score for the given session-team as an Lstr. (properly formatted for the score type.) @@ -106,6 +107,7 @@ class GameResults: from ba._language import Lstr from ba._generated.enums import TimeFormat from ba._score import ScoreType + if not self._game_set: raise RuntimeError("Can't get team-score-str until game is set.") for score in list(self._scores.values()): @@ -113,13 +115,15 @@ class GameResults: if score[1] is None: return Lstr(value='-') if self._scoretype is ScoreType.SECONDS: - return timestring(score[1] * 1000, - centi=False, - timeformat=TimeFormat.MILLISECONDS) + return timestring( + score[1] * 1000, + centi=False, + timeformat=TimeFormat.MILLISECONDS, + ) if self._scoretype is ScoreType.MILLISECONDS: - return timestring(score[1], - centi=True, - timeformat=TimeFormat.MILLISECONDS) + return timestring( + score[1], centi=True, timeformat=TimeFormat.MILLISECONDS + ) return Lstr(value=str(score[1])) return Lstr(value='-') @@ -174,7 +178,8 @@ class GameResults: # Group by best scoring teams. winners: dict[int, list[ba.SessionTeam]] = {} scores = [ - score for score in self._scores.values() + score + for score in self._scores.values() if score[0]() is not None and score[1] is not None ] for score in scores: @@ -183,10 +188,13 @@ class GameResults: team = score[0]() assert team is not None sval.append(team) - results: list[tuple[int | None, - list[ba.SessionTeam]]] = list(winners.items()) - results.sort(reverse=not self._lower_is_better, - key=lambda x: asserttype(x[0], int)) + results: list[tuple[int | None, list[ba.SessionTeam]]] = list( + winners.items() + ) + results.sort( + reverse=not self._lower_is_better, + key=lambda x: asserttype(x[0], int), + ) # Also group the 'None' scores. none_sessionteams: list[ba.SessionTeam] = [] diff --git a/dist/ba_data/python/ba/_gameutils.py b/dist/ba_data/python/ba/_gameutils.py index 97cac63..2d73d25 100644 --- a/dist/ba_data/python/ba/_gameutils.py +++ b/dist/ba_data/python/ba/_gameutils.py @@ -21,7 +21,7 @@ TROPHY_CHARS = { '3': SpecialChar.TROPHY3, '0a': SpecialChar.TROPHY0A, '0b': SpecialChar.TROPHY0B, - '4': SpecialChar.TROPHY4 + '4': SpecialChar.TROPHY4, } @@ -31,6 +31,7 @@ class GameTip: Category: **Gameplay Classes** """ + text: str icon: ba.Texture | None = None sound: ba.Sound | None = None @@ -43,14 +44,16 @@ def get_trophy_string(trophy_id: str) -> str: return '?' -def animate(node: ba.Node, - attr: str, - keys: dict[float, float], - loop: bool = False, - offset: float = 0, - timetype: ba.TimeType = TimeType.SIM, - timeformat: ba.TimeFormat = TimeFormat.SECONDS, - suppress_format_warning: bool = False) -> ba.Node: +def animate( + node: ba.Node, + attr: str, + keys: dict[float, float], + loop: bool = False, + offset: float = 0, + timetype: ba.TimeType = TimeType.SIM, + timeformat: ba.TimeFormat = TimeFormat.SECONDS, + suppress_format_warning: bool = False, +) -> ba.Node: """Animate values on a target ba.Node. Category: **Gameplay Functions** @@ -76,9 +79,11 @@ def animate(node: ba.Node, for item in items: _ba.time_format_check(timeformat, item[0]) - curve = _ba.newnode('animcurve', - owner=node, - name='Driving ' + str(node) + ' \'' + attr + '\'') + curve = _ba.newnode( + 'animcurve', + owner=node, + name='Driving ' + str(node) + ' \'' + attr + '\'', + ) if timeformat is TimeFormat.SECONDS: mult = 1000 @@ -89,7 +94,8 @@ def animate(node: ba.Node, curve.times = [int(mult * time) for time, val in items] curve.offset = _ba.time(timeformat=TimeFormat.MILLISECONDS) + int( - mult * offset) + mult * offset + ) curve.values = [val for time, val in items] curve.loop = loop @@ -99,9 +105,11 @@ def animate(node: ba.Node, # get disconnected. if not loop: # noinspection PyUnresolvedReferences - _ba.timer(int(mult * items[-1][0]) + 1000, - curve.delete, - timeformat=TimeFormat.MILLISECONDS) + _ba.timer( + int(mult * items[-1][0]) + 1000, + curve.delete, + timeformat=TimeFormat.MILLISECONDS, + ) # Do the connects last so all our attrs are in place when we push initial # values through. @@ -117,15 +125,17 @@ def animate(node: ba.Node, return curve -def animate_array(node: ba.Node, - attr: str, - size: int, - keys: dict[float, Sequence[float]], - loop: bool = False, - offset: float = 0, - timetype: ba.TimeType = TimeType.SIM, - timeformat: ba.TimeFormat = TimeFormat.SECONDS, - suppress_format_warning: bool = False) -> None: +def animate_array( + node: ba.Node, + attr: str, + size: int, + keys: dict[float, Sequence[float]], + loop: bool = False, + offset: float = 0, + timetype: ba.TimeType = TimeType.SIM, + timeformat: ba.TimeFormat = TimeFormat.SECONDS, + suppress_format_warning: bool = False, +) -> None: """Animate an array of values on a target ba.Node. Category: **Gameplay Functions** @@ -163,15 +173,19 @@ def animate_array(node: ba.Node, globalsnode = _ba.getsession().sessionglobalsnode for i in range(size): - curve = _ba.newnode('animcurve', - owner=node, - name=('Driving ' + str(node) + ' \'' + attr + - '\' member ' + str(i))) + curve = _ba.newnode( + 'animcurve', + owner=node, + name=( + 'Driving ' + str(node) + ' \'' + attr + '\' member ' + str(i) + ), + ) globalsnode.connectattr(driver, curve, 'in') curve.times = [int(mult * time) for time, val in items] curve.values = [val[i] for time, val in items] curve.offset = _ba.time(timeformat=TimeFormat.MILLISECONDS) + int( - mult * offset) + mult * offset + ) curve.loop = loop curve.connectattr('out', combine, 'input' + str(i)) @@ -180,9 +194,11 @@ def animate_array(node: ba.Node, if not loop: # (PyCharm seems to think item is a float, not a tuple) # noinspection PyUnresolvedReferences - _ba.timer(int(mult * items[-1][0]) + 1000, - curve.delete, - timeformat=TimeFormat.MILLISECONDS) + _ba.timer( + int(mult * items[-1][0]) + 1000, + curve.delete, + timeformat=TimeFormat.MILLISECONDS, + ) combine.connectattr('output', node, attr) # If we're not looping, set a timer to kill the combine once @@ -192,13 +208,16 @@ def animate_array(node: ba.Node, if not loop: # (PyCharm seems to think item is a float, not a tuple) # noinspection PyUnresolvedReferences - _ba.timer(int(mult * items[-1][0]) + 1000, - combine.delete, - timeformat=TimeFormat.MILLISECONDS) + _ba.timer( + int(mult * items[-1][0]) + 1000, + combine.delete, + timeformat=TimeFormat.MILLISECONDS, + ) -def show_damage_count(damage: str, position: Sequence[float], - direction: Sequence[float]) -> None: +def show_damage_count( + damage: str, position: Sequence[float], direction: Sequence[float] +) -> None: """Pop up a damage count at a position in space. Category: **Gameplay Functions** @@ -210,16 +229,18 @@ def show_damage_count(damage: str, position: Sequence[float], # (connected clients may have differing configs so they won't # get the intended results). do_big = app.ui.uiscale is UIScale.SMALL or app.vr_mode - txtnode = _ba.newnode('text', - attrs={ - 'text': damage, - 'in_world': True, - 'h_align': 'center', - 'flatness': 1.0, - 'shadow': 1.0 if do_big else 0.7, - 'color': (1, 0.25, 0.25, 1), - 'scale': 0.015 if do_big else 0.01 - }) + txtnode = _ba.newnode( + 'text', + attrs={ + 'text': damage, + 'in_world': True, + 'h_align': 'center', + 'flatness': 1.0, + 'shadow': 1.0 if do_big else 0.7, + 'color': (1, 0.25, 0.25, 1), + 'scale': 0.015 if do_big else 0.01, + }, + ) # Translate upward. tcombine = _ba.newnode('combine', owner=txtnode, attrs={'size': 3}) tcombine.connectattr('output', txtnode, 'position') @@ -233,27 +254,35 @@ def show_damage_count(damage: str, position: Sequence[float], vval *= 0.5 p_start = position[0] p_dir = direction[0] - animate(tcombine, 'input0', - {i[0] * lifespan: p_start + p_dir * i[1] - for i in v_vals}) + animate( + tcombine, + 'input0', + {i[0] * lifespan: p_start + p_dir * i[1] for i in v_vals}, + ) p_start = position[1] p_dir = direction[1] - animate(tcombine, 'input1', - {i[0] * lifespan: p_start + p_dir * i[1] - for i in v_vals}) + animate( + tcombine, + 'input1', + {i[0] * lifespan: p_start + p_dir * i[1] for i in v_vals}, + ) p_start = position[2] p_dir = direction[2] - animate(tcombine, 'input2', - {i[0] * lifespan: p_start + p_dir * i[1] - for i in v_vals}) + animate( + tcombine, + 'input2', + {i[0] * lifespan: p_start + p_dir * i[1] for i in v_vals}, + ) animate(txtnode, 'opacity', {0.7 * lifespan: 1.0, lifespan: 0.0}) _ba.timer(lifespan, txtnode.delete) -def timestring(timeval: float, - centi: bool = True, - timeformat: ba.TimeFormat = TimeFormat.SECONDS, - suppress_format_warning: bool = False) -> ba.Lstr: +def timestring( + timeval: float, + centi: bool = True, + timeformat: ba.TimeFormat = TimeFormat.SECONDS, + suppress_format_warning: bool = False, +) -> ba.Lstr: """Generate a ba.Lstr for displaying a time value. Category: **General Utility Functions** @@ -292,32 +321,56 @@ def timestring(timeval: float, hval = (timeval // 1000) // (60 * 60) if hval != 0: bits.append('${H}') - subs.append(('${H}', - Lstr(resource='timeSuffixHoursText', - subs=[('${COUNT}', str(hval))]))) + subs.append( + ( + '${H}', + Lstr( + resource='timeSuffixHoursText', + subs=[('${COUNT}', str(hval))], + ), + ) + ) mval = ((timeval // 1000) // 60) % 60 if mval != 0: bits.append('${M}') - subs.append(('${M}', - Lstr(resource='timeSuffixMinutesText', - subs=[('${COUNT}', str(mval))]))) + subs.append( + ( + '${M}', + Lstr( + resource='timeSuffixMinutesText', + subs=[('${COUNT}', str(mval))], + ), + ) + ) # We add seconds if its non-zero *or* we haven't added anything else. if centi: # pylint: disable=consider-using-f-string - sval = (timeval / 1000.0 % 60.0) + sval = timeval / 1000.0 % 60.0 if sval >= 0.005 or not bits: bits.append('${S}') - subs.append(('${S}', - Lstr(resource='timeSuffixSecondsText', - subs=[('${COUNT}', ('%.2f' % sval))]))) + subs.append( + ( + '${S}', + Lstr( + resource='timeSuffixSecondsText', + subs=[('${COUNT}', ('%.2f' % sval))], + ), + ) + ) else: - sval = (timeval // 1000 % 60) + sval = timeval // 1000 % 60 if sval != 0 or not bits: bits.append('${S}') - subs.append(('${S}', - Lstr(resource='timeSuffixSecondsText', - subs=[('${COUNT}', str(sval))]))) + subs.append( + ( + '${S}', + Lstr( + resource='timeSuffixSecondsText', + subs=[('${COUNT}', str(sval))], + ), + ) + ) return Lstr(value=' '.join(bits), subs=subs) @@ -332,11 +385,17 @@ def cameraflash(duration: float = 999.0) -> None: # pylint: disable=too-many-locals import random from ba._nodeactor import NodeActor + x_spread = 10 y_spread = 5 - positions = [[-x_spread, -y_spread], [0, -y_spread], [0, y_spread], - [x_spread, -y_spread], [x_spread, y_spread], - [-x_spread, y_spread]] + positions = [ + [-x_spread, -y_spread], + [0, -y_spread], + [0, y_spread], + [x_spread, -y_spread], + [x_spread, y_spread], + [-x_spread, y_spread], + ] times = [0, 2700, 1000, 1800, 500, 1400] # Store this on the current activity so we only have one at a time. @@ -345,57 +404,73 @@ def cameraflash(duration: float = 999.0) -> None: activity.camera_flash_data = [] # type: ignore for i in range(6): light = NodeActor( - _ba.newnode('light', - attrs={ - 'position': (positions[i][0], 0, positions[i][1]), - 'radius': 1.0, - 'lights_volumes': False, - 'height_attenuated': False, - 'color': (0.2, 0.2, 0.8) - })) + _ba.newnode( + 'light', + attrs={ + 'position': (positions[i][0], 0, positions[i][1]), + 'radius': 1.0, + 'lights_volumes': False, + 'height_attenuated': False, + 'color': (0.2, 0.2, 0.8), + }, + ) + ) sval = 1.87 iscale = 1.3 - tcombine = _ba.newnode('combine', - owner=light.node, - attrs={ - 'size': 3, - 'input0': positions[i][0], - 'input1': 0, - 'input2': positions[i][1] - }) + tcombine = _ba.newnode( + 'combine', + owner=light.node, + attrs={ + 'size': 3, + 'input0': positions[i][0], + 'input1': 0, + 'input2': positions[i][1], + }, + ) assert light.node tcombine.connectattr('output', light.node, 'position') xval = positions[i][0] yval = positions[i][1] spd = 0.5 + random.random() spd2 = 0.5 + random.random() - animate(tcombine, - 'input0', { - 0.0: xval + 0, - 0.069 * spd: xval + 10.0, - 0.143 * spd: xval - 10.0, - 0.201 * spd: xval + 0 - }, - loop=True) - animate(tcombine, - 'input2', { - 0.0: yval + 0, - 0.15 * spd2: yval + 10.0, - 0.287 * spd2: yval - 10.0, - 0.398 * spd2: yval + 0 - }, - loop=True) - animate(light.node, - 'intensity', { - 0.0: 0, - 0.02 * sval: 0, - 0.05 * sval: 0.8 * iscale, - 0.08 * sval: 0, - 0.1 * sval: 0 - }, - loop=True, - offset=times[i]) - _ba.timer((times[i] + random.randint(1, int(duration)) * 40 * sval), - light.node.delete, - timeformat=TimeFormat.MILLISECONDS) + animate( + tcombine, + 'input0', + { + 0.0: xval + 0, + 0.069 * spd: xval + 10.0, + 0.143 * spd: xval - 10.0, + 0.201 * spd: xval + 0, + }, + loop=True, + ) + animate( + tcombine, + 'input2', + { + 0.0: yval + 0, + 0.15 * spd2: yval + 10.0, + 0.287 * spd2: yval - 10.0, + 0.398 * spd2: yval + 0, + }, + loop=True, + ) + animate( + light.node, + 'intensity', + { + 0.0: 0, + 0.02 * sval: 0, + 0.05 * sval: 0.8 * iscale, + 0.08 * sval: 0, + 0.1 * sval: 0, + }, + loop=True, + offset=times[i], + ) + _ba.timer( + (times[i] + random.randint(1, int(duration)) * 40 * sval), + light.node.delete, + timeformat=TimeFormat.MILLISECONDS, + ) activity.camera_flash_data.append(light) # type: ignore diff --git a/dist/ba_data/python/ba/_general.py b/dist/ba_data/python/ba/_general.py index f40fa09..bb99231 100644 --- a/dist/ba_data/python/ba/_general.py +++ b/dist/ba_data/python/ba/_general.py @@ -3,7 +3,6 @@ """Utility snippets applying to generic Python code.""" from __future__ import annotations -import gc import types import weakref import random @@ -16,7 +15,6 @@ from ba._error import print_error, print_exception from ba._generated.enums import TimeType if TYPE_CHECKING: - from types import FrameType from typing import Any from efro.call import Call as Call # 'as Call' so we re-export. @@ -64,6 +62,7 @@ def getclass(name: str, subclassof: type[T]) -> type[T]: 'subclassof' class, and a TypeError will be raised if not. """ import importlib + splits = name.split('.') modulename = '.'.join(splits[:-1]) classname = splits[-1] @@ -84,8 +83,10 @@ def json_prep(data: Any) -> Any: """ if isinstance(data, dict): - return dict((json_prep(key), json_prep(value)) - for key, value in list(data.items())) + return dict( + (json_prep(key), json_prep(value)) + for key, value in list(data.items()) + ) if isinstance(data, list): return [json_prep(element) for element in data] if isinstance(data, tuple): @@ -96,19 +97,23 @@ def json_prep(data: Any) -> Any: return data.decode(errors='ignore') except Exception: from ba import _error + print_error('json_prep encountered utf-8 decode error', once=True) return data.decode(errors='ignore') if not isinstance(data, (str, float, bool, type(None), int)): - print_error('got unsupported type in json_prep:' + str(type(data)), - once=True) + print_error( + 'got unsupported type in json_prep:' + str(type(data)), once=True + ) return data def utf8_all(data: Any) -> Any: """Convert any unicode data in provided sequence(s) to utf8 bytes.""" if isinstance(data, dict): - return dict((utf8_all(key), utf8_all(value)) - for key, value in list(data.items())) + return dict( + (utf8_all(key), utf8_all(value)) + for key, value in list(data.items()) + ) if isinstance(data, list): return [utf8_all(element) for element in data] if isinstance(data, tuple): @@ -118,19 +123,6 @@ def utf8_all(data: Any) -> Any: return data -def print_refs(obj: Any) -> None: - """Print a list of known live references to an object.""" - - # Hmmm; I just noticed that calling this on an object - # seems to keep it alive. Should figure out why. - print('REFERENCES FOR', obj, ':') - refs = list(gc.get_referrers(obj)) - i = 1 - for ref in refs: - print(' ref', i, ':', ref) - i += 1 - - def get_type_name(cls: type) -> str: """Return a full type name including module for a class.""" return cls.__module__ + '.' + cls.__name__ @@ -190,11 +182,17 @@ class _WeakCall: else: app = _ba.app if not app.did_weak_call_warning: - print(('Warning: callable passed to ba.WeakCall() is not' - ' weak-referencable (' + str(args[0]) + - '); use ba.Call() instead to avoid this ' - 'warning. Stack-trace:')) + print( + ( + 'Warning: callable passed to ba.WeakCall() is not' + ' weak-referencable (' + + str(args[0]) + + '); use ba.Call() instead to avoid this ' + 'warning. Stack-trace:' + ) + ) import traceback + traceback.print_stack() app.did_weak_call_warning = True self._call = args[0] @@ -205,8 +203,15 @@ class _WeakCall: return self._call(*self._args + args_extra, **self._keywds) def __str__(self) -> str: - return ('') + return ( + '' + ) class _Call: @@ -244,8 +249,15 @@ class _Call: return self._call(*self._args + args_extra, **self._keywds) def __str__(self) -> str: - return ('') + return ( + '' + ) if TYPE_CHECKING: @@ -278,7 +290,7 @@ class WeakMethod: obj = self._obj() if obj is None: return None - return self._func(*((obj, ) + args), **keywds) + return self._func(*((obj,) + args), **keywds) def __str__(self) -> str: return '' @@ -300,50 +312,9 @@ def verify_object_death(obj: object) -> None: # if we queue a lot of them. delay = random.uniform(2.0, 5.5) with _ba.Context('ui'): - _ba.timer(delay, - lambda: _verify_object_death(ref), - timetype=TimeType.REAL) - - -def print_active_refs(obj: Any) -> None: - """Print info about things referencing a given object. - - Category: **General Utility Functions** - - Useful for tracking down cyclical references and causes for zombie objects. - """ - # pylint: disable=too-many-nested-blocks - from types import FrameType, TracebackType - refs = list(gc.get_referrers(obj)) - print(f'{Clr.YLW}Active referrers to {obj}:{Clr.RST}') - for i, ref in enumerate(refs): - print(f'{Clr.YLW}#{i+1}:{Clr.BLU} {ref}{Clr.RST}') - - # For certain types of objects such as stack frames, show what is - # keeping *them* alive too. - if isinstance(ref, FrameType): - print(f'{Clr.YLW} Active referrers to #{i+1}:{Clr.RST}') - refs2 = list(gc.get_referrers(ref)) - for j, ref2 in enumerate(refs2): - print(f'{Clr.YLW} #a{j+1}:{Clr.BLU} {ref2}{Clr.RST}') - - # Can go further down the rabbit-hole if needed... - if bool(False): - if isinstance(ref2, TracebackType): - print(f'{Clr.YLW} ' - f'Active referrers to #a{j+1}:{Clr.RST}') - refs3 = list(gc.get_referrers(ref2)) - for k, ref3 in enumerate(refs3): - print(f'{Clr.YLW} ' - f'#b{k+1}:{Clr.BLU} {ref3}{Clr.RST}') - - if isinstance(ref3, BaseException): - print(f'{Clr.YLW} Active referrers to' - f' #b{k+1}:{Clr.RST}') - refs4 = list(gc.get_referrers(ref3)) - for x, ref4 in enumerate(refs4): - print(f'{Clr.YLW} #c{x+1}:{Clr.BLU}' - f' {ref4}{Clr.RST}') + _ba.timer( + delay, lambda: _verify_object_death(ref), timetype=TimeType.REAL + ) def _verify_object_death(wref: weakref.ref) -> None: @@ -357,9 +328,11 @@ def _verify_object_death(wref: weakref.ref) -> None: print(f'Note: unable to get type name for {obj}') name = 'object' - print(f'{Clr.RED}Error: {name} not dying when expected to:' - f' {Clr.BLD}{obj}{Clr.RST}') - print_active_refs(obj) + print( + f'{Clr.RED}Error: {name} not dying when expected to:' + f' {Clr.BLD}{obj}{Clr.RST}\n' + 'See efro.debug for ways to debug this.' + ) def storagename(suffix: str | None = None) -> str: diff --git a/dist/ba_data/python/ba/_generated/enums.py b/dist/ba_data/python/ba/_generated/enums.py index 74a11bb..58fb79a 100644 --- a/dist/ba_data/python/ba/_generated/enums.py +++ b/dist/ba_data/python/ba/_generated/enums.py @@ -10,6 +10,7 @@ class InputType(Enum): Category: Enums """ + UP_DOWN = 2 LEFT_RIGHT = 3 JUMP_PRESS = 4 @@ -57,6 +58,7 @@ class UIScale(Enum): content needs to be presented as large and clear in order to remain readable from an average distance. """ + LARGE = 0 MEDIUM = 1 SMALL = 2 @@ -79,6 +81,7 @@ class TimeType(Enum): not advance while the app is backgrounded for instance. (the engine attempts to prevent single large time jumps from occurring) """ + SIM = 0 BASE = 1 REAL = 2 @@ -89,6 +92,7 @@ class TimeFormat(Enum): Category: Enums """ + SECONDS = 0 MILLISECONDS = 1 @@ -98,6 +102,7 @@ class Permission(Enum): Category: Enums """ + STORAGE = 0 @@ -106,6 +111,7 @@ class SpecialChar(Enum): Category: Enums """ + DOWN_ARROW = 0 UP_ARROW = 1 LEFT_ARROW = 2 diff --git a/dist/ba_data/python/ba/_hooks.py b/dist/ba_data/python/ba/_hooks.py index e02ba22..2f977bf 100644 --- a/dist/ba_data/python/ba/_hooks.py +++ b/dist/ba_data/python/ba/_hooks.py @@ -54,87 +54,113 @@ def set_config_fullscreen_off() -> None: def not_signed_in_screen_message() -> None: from ba._language import Lstr + _ba.screenmessage(Lstr(resource='notSignedInErrorText')) def connecting_to_party_message() -> None: from ba._language import Lstr - _ba.screenmessage(Lstr(resource='internal.connectingToPartyText'), - color=(1, 1, 1)) + + _ba.screenmessage( + Lstr(resource='internal.connectingToPartyText'), color=(1, 1, 1) + ) def rejecting_invite_already_in_party_message() -> None: from ba._language import Lstr + _ba.screenmessage( Lstr(resource='internal.rejectingInviteAlreadyInPartyText'), - color=(1, 0.5, 0)) + color=(1, 0.5, 0), + ) def connection_failed_message() -> None: from ba._language import Lstr - _ba.screenmessage(Lstr(resource='internal.connectionFailedText'), - color=(1, 0.5, 0)) + + _ba.screenmessage( + Lstr(resource='internal.connectionFailedText'), color=(1, 0.5, 0) + ) def temporarily_unavailable_message() -> None: from ba._language import Lstr + _ba.playsound(_ba.getsound('error')) _ba.screenmessage( Lstr(resource='getTicketsWindow.unavailableTemporarilyText'), - color=(1, 0, 0)) + color=(1, 0, 0), + ) def in_progress_message() -> None: from ba._language import Lstr + _ba.playsound(_ba.getsound('error')) - _ba.screenmessage(Lstr(resource='getTicketsWindow.inProgressText'), - color=(1, 0, 0)) + _ba.screenmessage( + Lstr(resource='getTicketsWindow.inProgressText'), color=(1, 0, 0) + ) def error_message() -> None: from ba._language import Lstr + _ba.playsound(_ba.getsound('error')) _ba.screenmessage(Lstr(resource='errorText'), color=(1, 0, 0)) def purchase_not_valid_error() -> None: from ba._language import Lstr + _ba.playsound(_ba.getsound('error')) - _ba.screenmessage(Lstr(resource='store.purchaseNotValidError', - subs=[('${EMAIL}', 'support@froemling.net')]), - color=(1, 0, 0)) + _ba.screenmessage( + Lstr( + resource='store.purchaseNotValidError', + subs=[('${EMAIL}', 'support@froemling.net')], + ), + color=(1, 0, 0), + ) def purchase_already_in_progress_error() -> None: from ba._language import Lstr + _ba.playsound(_ba.getsound('error')) - _ba.screenmessage(Lstr(resource='store.purchaseAlreadyInProgressText'), - color=(1, 0, 0)) + _ba.screenmessage( + Lstr(resource='store.purchaseAlreadyInProgressText'), color=(1, 0, 0) + ) def gear_vr_controller_warning() -> None: from ba._language import Lstr + _ba.playsound(_ba.getsound('error')) - _ba.screenmessage(Lstr(resource='usesExternalControllerText'), - color=(1, 0, 0)) + _ba.screenmessage( + Lstr(resource='usesExternalControllerText'), color=(1, 0, 0) + ) def uuid_str() -> str: import uuid + return str(uuid.uuid4()) def orientation_reset_cb_message() -> None: from ba._language import Lstr + _ba.screenmessage( Lstr(resource='internal.vrOrientationResetCardboardText'), - color=(0, 1, 0)) + color=(0, 1, 0), + ) def orientation_reset_message() -> None: from ba._language import Lstr - _ba.screenmessage(Lstr(resource='internal.vrOrientationResetText'), - color=(0, 1, 0)) + + _ba.screenmessage( + Lstr(resource='internal.vrOrientationResetText'), color=(0, 1, 0) + ) def on_app_pause() -> None: @@ -147,12 +173,14 @@ def on_app_resume() -> None: def launch_main_menu_session() -> None: from bastd.mainmenu import MainMenuSession + _ba.new_host_session(MainMenuSession) def language_test_toggle() -> None: - _ba.app.lang.setlanguage('Gibberish' if _ba.app.lang.language == - 'English' else 'English') + _ba.app.lang.setlanguage( + 'Gibberish' if _ba.app.lang.language == 'English' else 'English' + ) def award_in_control_achievement() -> None: @@ -173,8 +201,10 @@ def launch_coop_game(name: str) -> None: def purchases_restored_message() -> None: from ba._language import Lstr - _ba.screenmessage(Lstr(resource='getTicketsWindow.purchasesRestoredText'), - color=(0, 1, 0)) + + _ba.screenmessage( + Lstr(resource='getTicketsWindow.purchasesRestoredText'), color=(0, 1, 0) + ) def dismiss_wii_remotes_window() -> None: @@ -185,8 +215,10 @@ def dismiss_wii_remotes_window() -> None: def unavailable_message() -> None: from ba._language import Lstr - _ba.screenmessage(Lstr(resource='getTicketsWindow.unavailableText'), - color=(1, 0, 0)) + + _ba.screenmessage( + Lstr(resource='getTicketsWindow.unavailableText'), color=(1, 0, 0) + ) def submit_analytics_counts(sval: str) -> None: @@ -196,19 +228,23 @@ def submit_analytics_counts(sval: str) -> None: def set_last_ad_network(sval: str) -> None: import time + _ba.app.ads.last_ad_network = sval _ba.app.ads.last_ad_network_set_time = time.time() def no_game_circle_message() -> None: from ba._language import Lstr + _ba.screenmessage(Lstr(resource='noGameCircleText'), color=(1, 0, 0)) def google_play_purchases_not_available_message() -> None: from ba._language import Lstr - _ba.screenmessage(Lstr(resource='googlePlayPurchasesNotAvailableText'), - color=(1, 0, 0)) + + _ba.screenmessage( + Lstr(resource='googlePlayPurchasesNotAvailableText'), color=(1, 0, 0) + ) def empty_call() -> None: @@ -229,8 +265,10 @@ def coin_icon_press() -> None: def ticket_icon_press() -> None: from bastd.ui.resourcetypeinfo import ResourceTypeInfoWindow + ResourceTypeInfoWindow( - origin_widget=_ba.get_special_widget('tickets_info_button')) + origin_widget=_ba.get_special_widget('tickets_info_button') + ) def back_button_press() -> None: @@ -243,6 +281,7 @@ def friends_button_press() -> None: def print_trace() -> None: import traceback + print('Python Traceback (most recent call last):') traceback.print_stack() @@ -256,6 +295,7 @@ def toggle_fullscreen() -> None: def party_icon_activate(origin: Sequence[float]) -> None: import weakref from bastd.ui.party import PartyWindow + app = _ba.app _ba.playsound(_ba.getsound('swish')) @@ -276,13 +316,16 @@ def ui_remote_press() -> None: # Can be called without a context; need a context for getsound. with _ba.Context('ui'): - _ba.screenmessage(Lstr(resource='internal.controllerForMenusOnlyText'), - color=(1, 0, 0)) + _ba.screenmessage( + Lstr(resource='internal.controllerForMenusOnlyText'), + color=(1, 0, 0), + ) _ba.playsound(_ba.getsound('error')) def quit_window() -> None: from bastd.ui.confirm import QuitWindow + QuitWindow() @@ -292,6 +335,7 @@ def remove_in_game_ads_message() -> None: def telnet_access_request() -> None: from bastd.ui.telnet import TelnetAccessRequestWindow + TelnetAccessRequestWindow() @@ -305,11 +349,13 @@ def shutdown() -> None: def gc_disable() -> None: import gc + gc.disable() def device_menu_press(device: ba.InputDevice) -> None: from bastd.ui.mainmenu import MainMenuWindow + in_main_menu = _ba.app.ui.has_main_menu_window() if not in_main_menu: _ba.set_ui_input_device(device) @@ -319,6 +365,7 @@ def device_menu_press(device: ba.InputDevice) -> None: def show_url_window(address: str) -> None: from bastd.ui.url import ShowURLWindow + ShowURLWindow(address) @@ -328,8 +375,9 @@ def party_invite_revoke(invite_id: str) -> None: for winref in _ba.app.invite_confirm_windows: win = winref() if win is not None and win.ew_party_invite_id == invite_id: - _ba.containerwidget(edit=win.get_root_widget(), - transition='out_right') + _ba.containerwidget( + edit=win.get_root_widget(), transition='out_right' + ) import custom_hooks as chooks def filter_chat_message(msg: str, client_id: int) -> str | None: @@ -370,8 +418,10 @@ def on_player_leave(pb_id:str)-> None: def local_chat_message(msg: str) -> None: - if (_ba.app.ui.party_window is not None - and _ba.app.ui.party_window() is not None): + if ( + _ba.app.ui.party_window is not None + and _ba.app.ui.party_window() is not None + ): _ba.app.ui.party_window().on_chat_message(msg) @@ -381,13 +431,14 @@ def get_player_icon(sessionplayer: ba.SessionPlayer) -> dict[str, Any]: 'texture': _ba.gettexture(info['texture']), 'tint_texture': _ba.gettexture(info['tint_texture']), 'tint_color': info['tint_color'], - 'tint2_color': info['tint2_color'] + 'tint2_color': info['tint2_color'], } def hash_strings(inputs: list[str]) -> str: """Hash provided strings into a short output string.""" import hashlib + sha = hashlib.sha1() for inp in inputs: sha.update(inp.encode()) diff --git a/dist/ba_data/python/ba/_input.py b/dist/ba_data/python/ba/_input.py index fc21a43..682dd04 100644 --- a/dist/ba_data/python/ba/_input.py +++ b/dist/ba_data/python/ba/_input.py @@ -48,7 +48,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any: 'buttonRight': 23, 'buttonUp': 20, 'buttonDown': 21, - 'buttonVRReorient': 110 + 'buttonVRReorient': 110, }.get(name, -1) # If there's an entry in our config for this controller, use it. @@ -79,7 +79,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any: 'buttonRun2': 5, 'buttonRun1': 6, 'buttonJump': 1, - 'buttonIgnored': 11 + 'buttonIgnored': 11, }.get(name, -1) # Ps4 controller. @@ -94,7 +94,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any: 'buttonPunch': 1, 'buttonRun2': 5, 'buttonRun1': 6, - 'triggerRun1': 5 + 'triggerRun1': 5, }.get(name, -1) # Look for some exact types. @@ -112,7 +112,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any: 'buttonPunch': 100, 'buttonRun2': 103, 'buttonRun1': 104, - 'triggerRun1': 24 + 'triggerRun1': 24, }.get(name, -1) if devicename == 'NYKO PLAYPAD PRO': return { @@ -126,7 +126,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any: 'buttonRight': 23, 'buttonStart': 83, 'buttonPunch': 100, - 'buttonDown': 21 + 'buttonDown': 21, }.get(name, -1) if devicename == 'Logitech Dual Action': return { @@ -136,7 +136,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any: 'buttonBomb': 101, 'buttonJump': 100, 'buttonStart': 109, - 'buttonPunch': 97 + 'buttonPunch': 97, }.get(name, -1) if devicename == 'Xbox 360 Wireless Receiver': return { @@ -150,7 +150,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any: 'buttonRight': 23, 'buttonStart': 83, 'buttonPunch': 100, - 'buttonDown': 21 + 'buttonDown': 21, }.get(name, -1) if devicename == 'Microsoft X-Box 360 pad': return { @@ -160,11 +160,12 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any: 'buttonBomb': 98, 'buttonJump': 97, 'buttonStart': 83, - 'buttonPunch': 100 + 'buttonPunch': 100, }.get(name, -1) if devicename in [ - 'Amazon Remote', 'Amazon Bluetooth Dev', - 'Amazon Fire TV Remote' + 'Amazon Remote', + 'Amazon Bluetooth Dev', + 'Amazon Fire TV Remote', ]: return { 'triggerRun2': 23, @@ -178,7 +179,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any: 'buttonRight': 23, 'buttonStart': 83, 'buttonPunch': 90, - 'buttonDown': 21 + 'buttonDown': 21, }.get(name, -1) elif 'NVIDIA SHIELD;' in useragentstring: @@ -193,7 +194,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any: 'buttonStart': 109, 'buttonPunch': 100, 'buttonIgnored': 184, - 'buttonIgnored2': 86 + 'buttonIgnored2': 86, }.get(name, -1) elif platform == 'mac': if devicename == 'PLAYSTATION(R)3 Controller': @@ -207,7 +208,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any: 'buttonBomb': 14, 'buttonPickUp': 13, 'buttonStart': 4, - 'buttonIgnored': 17 + 'buttonIgnored': 17, }.get(name, -1) if devicename in ['Wireless 360 Controller', 'Controller']: @@ -225,16 +226,18 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any: 'buttonUp': 1, 'triggerRun1': 5, 'triggerRun2': 6, - 'buttonIgnored': 11 + 'buttonIgnored': 11, }.get(name, -1) - if (devicename - in ['Logitech Dual Action', 'Logitech Cordless RumblePad 2']): + if devicename in [ + 'Logitech Dual Action', + 'Logitech Cordless RumblePad 2', + ]: return { 'buttonJump': 2, 'buttonPunch': 1, 'buttonBomb': 3, 'buttonPickUp': 4, - 'buttonStart': 10 + 'buttonStart': 10, }.get(name, -1) # Old gravis gamepad. @@ -244,7 +247,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any: 'buttonPunch': 1, 'buttonBomb': 3, 'buttonPickUp': 4, - 'buttonStart': 10 + 'buttonStart': 10, }.get(name, -1) if devicename == 'Microsoft SideWinder Plug & Play Game Pad': @@ -253,7 +256,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any: 'buttonPunch': 3, 'buttonBomb': 2, 'buttonPickUp': 4, - 'buttonStart': 6 + 'buttonStart': 6, }.get(name, -1) # Saitek P2500 Rumble Force Pad.. (hopefully works for others too?..) @@ -263,7 +266,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any: 'buttonPunch': 1, 'buttonBomb': 4, 'buttonPickUp': 2, - 'buttonStart': 11 + 'buttonStart': 11, }.get(name, -1) # Some crazy 'Senze' dual gamepad. @@ -288,7 +291,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any: 'buttonStart': 10, 'buttonStart_B': 22, 'enableSecondary': 1, - 'unassignedButtonsRun': False + 'unassignedButtonsRun': False, }.get(name, -1) if devicename == 'USB Gamepad ': # some weird 'JITE' gamepad return { @@ -298,7 +301,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any: 'buttonPunch': 4, 'buttonBomb': 2, 'buttonPickUp': 1, - 'buttonStart': 10 + 'buttonStart': 10, }.get(name, -1) default_android_mapping = { @@ -317,7 +320,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any: 'buttonRight': 23, 'buttonUp': 20, 'buttonDown': 21, - 'buttonVRReorient': 110 + 'buttonVRReorient': 110, } # Generic android... @@ -341,7 +344,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any: 'buttonRight': 23, 'buttonUp': 20, 'buttonDown': 21, - 'buttonVRReorient': 108 + 'buttonVRReorient': 108, }.get(name, -1) # Adt-1 gamepad (use funky 'mode' button for start). @@ -357,7 +360,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any: 'startButtonActivatesDefaultWidget': False, 'buttonRun2': 104, 'buttonRun1': 103, - 'triggerRun1': 18 + 'triggerRun1': 18, }.get(name, -1) # Nexus player remote. if devicename == 'Nexus Remote': @@ -376,7 +379,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any: 'buttonPunch': 24, 'buttonRun2': 104, 'buttonRun1': 103, - 'triggerRun1': 18 + 'triggerRun1': 18, }.get(name, -1) if devicename == 'virtual-remote': @@ -397,13 +400,15 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any: 'buttonRun1': 103, 'buttonDown': 21, 'startButtonActivatesDefaultWidget': False, - 'uiOnly': True + 'uiOnly': True, }.get(name, -1) # flag particular gamepads to use exact android defaults.. # (so they don't even ask to configure themselves) - if devicename in ['Samsung Game Pad EI-GP20', 'ASUS Gamepad' - ] or devicename.startswith('Freefly VR Glide'): + if devicename in [ + 'Samsung Game Pad EI-GP20', + 'ASUS Gamepad', + ] or devicename.startswith('Freefly VR Glide'): return default_android_mapping.get(name, -1) # Nvidia controller is default, but gets some strange @@ -423,7 +428,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any: 'buttonPunch': 100, 'buttonRun2': 104, 'buttonRun1': 103, - 'triggerRun1': 18 + 'triggerRun1': 18, }.get(name, -1) # Default keyboard vals across platforms.. @@ -438,7 +443,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any: 'buttonDown': 274, 'buttonLeft': 276, 'buttonRight': 275, - 'buttonStart': 263 + 'buttonStart': 263, }.get(name, -1) return { 'buttonPickUp': 1073741917, @@ -449,7 +454,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any: 'buttonRight': 1073741903, 'buttonStart': 1073741919, 'buttonPunch': 1073741913, - 'buttonDown': 1073741905 + 'buttonDown': 1073741905, }.get(name, -1) if devicename == 'Keyboard' and unique_id == '#1': return { @@ -460,7 +465,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any: 'buttonUp': 119, 'buttonDown': 115, 'buttonLeft': 97, - 'buttonRight': 100 + 'buttonRight': 100, }.get(name, -1) # Ok, this gamepad's not in our specific preset list; @@ -479,7 +484,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any: 'buttonPunch': 16, 'buttonBomb': 14, 'buttonPickUp': 13, - 'buttonStart': 4 + 'buttonStart': 4, }.get(name, -1) # Dual Action Config - hopefully applies to more... @@ -489,7 +494,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any: 'buttonPunch': 1, 'buttonBomb': 3, 'buttonPickUp': 4, - 'buttonStart': 10 + 'buttonStart': 10, }.get(name, -1) # Saitek P2500 Rumble Force Pad.. (hopefully works for others too?..) @@ -499,7 +504,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any: 'buttonPunch': 1, 'buttonBomb': 4, 'buttonPickUp': 2, - 'buttonStart': 11 + 'buttonStart': 11, }.get(name, -1) # Gravis stuff?... @@ -509,7 +514,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any: 'buttonPunch': 1, 'buttonBomb': 3, 'buttonPickUp': 4, - 'buttonStart': 10 + 'buttonStart': 10, }.get(name, -1) # Reasonable defaults. @@ -542,21 +547,23 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any: 'buttonPunch': 2, 'buttonBomb': 3, 'buttonPickUp': 4, - 'buttonStart': 5 + 'buttonStart': 5, }.get(name, -1) def _gen_android_input_hash() -> str: import os import hashlib + md5 = hashlib.md5() # Currently we just do a single hash of *all* inputs on android # and that's it.. good enough. # (grabbing mappings for a specific device looks to be non-trivial) for dirname in [ - '/system/usr/keylayout', '/data/usr/keylayout', - '/data/system/devices/keylayout' + '/system/usr/keylayout', + '/data/usr/keylayout', + '/data/system/devices/keylayout', ]: try: if os.path.isdir(dirname): @@ -573,8 +580,10 @@ def _gen_android_input_hash() -> str: pass except Exception: from ba import _error + _error.print_exception( - 'error in _gen_android_input_hash inner loop') + 'error in _gen_android_input_hash inner loop' + ) return md5.hexdigest() @@ -597,12 +606,14 @@ def get_input_map_hash(inputdevice: ba.InputDevice) -> str: return app.input_map_hash except Exception: from ba import _error + _error.print_exception('Exception in get_input_map_hash') return '' -def get_input_device_config(device: ba.InputDevice, - default: bool) -> tuple[dict, str]: +def get_input_device_config( + device: ba.InputDevice, default: bool +) -> tuple[dict, str]: """Given an input device, return its config dict in the app config. The dict will be created if it does not exist. @@ -634,8 +645,10 @@ def get_last_player_name_from_input_device(device: ba.InputDevice) -> str: # otherwise default to their current random name. profilename = '_random' key_name = device.name + ' ' + device.unique_identifier - if ('Default Player Profiles' in appconfig - and key_name in appconfig['Default Player Profiles']): + if ( + 'Default Player Profiles' in appconfig + and key_name in appconfig['Default Player Profiles'] + ): profilename = appconfig['Default Player Profiles'][key_name] if profilename == '_random': profilename = device.get_default_player_name() diff --git a/dist/ba_data/python/ba/_internal.py b/dist/ba_data/python/ba/_internal.py index 5637e6a..6d6e600 100644 --- a/dist/ba_data/python/ba/_internal.py +++ b/dist/ba_data/python/ba/_internal.py @@ -14,6 +14,7 @@ from typing import TYPE_CHECKING try: # noinspection PyUnresolvedReferences import _bainternal + HAVE_INTERNAL = True except ImportError: HAVE_INTERNAL = False @@ -26,6 +27,7 @@ if TYPE_CHECKING: # to account for its absence should call this to draw attention to itself. def _no_bainternal_warning() -> None: import logging + logging.warning('INTERNAL CALL RUN WITHOUT INTERNAL PRESENT.') @@ -47,8 +49,9 @@ def get_master_server_address(source: int = -1, version: int = 1) -> str: Return the address of the master server. """ if HAVE_INTERNAL: - return _bainternal.get_master_server_address(source=source, - version=version) + return _bainternal.get_master_server_address( + source=source, version=version + ) raise _no_bainternal_error() @@ -75,8 +78,9 @@ def game_service_has_leaderboard(game: str, config: str) -> bool: for it on the game service. """ if HAVE_INTERNAL: - return _bainternal.game_service_has_leaderboard(game=game, - config=config) + return _bainternal.game_service_has_leaderboard( + game=game, config=config + ) # Harmless to always just say no here. return False @@ -84,8 +88,9 @@ def game_service_has_leaderboard(game: str, config: str) -> bool: def report_achievement(achievement: str, pass_to_account: bool = True) -> None: """(internal)""" if HAVE_INTERNAL: - _bainternal.report_achievement(achievement=achievement, - pass_to_account=pass_to_account) + _bainternal.report_achievement( + achievement=achievement, pass_to_account=pass_to_account + ) return # Need to see if this actually still works as expected.. warning for now. @@ -93,17 +98,19 @@ def report_achievement(achievement: str, pass_to_account: bool = True) -> None: # noinspection PyUnresolvedReferences -def submit_score(game: str, - config: str, - name: Any, - score: int | None, - callback: Callable, - friend_callback: Callable | None, - order: str = 'increasing', - tournament_id: str | None = None, - score_type: str = 'points', - campaign: str | None = None, - level: str | None = None) -> None: +def submit_score( + game: str, + config: str, + name: Any, + score: int | None, + callback: Callable, + friend_callback: Callable | None, + order: str = 'increasing', + tournament_id: str | None = None, + score_type: str = 'points', + campaign: str | None = None, + level: str | None = None, +) -> None: """(internal) Submit a score to the server; callback will be called with the results. @@ -112,24 +119,27 @@ def submit_score(game: str, score server more mischief-proof. """ if HAVE_INTERNAL: - _bainternal.submit_score(game=game, - config=config, - name=name, - score=score, - callback=callback, - friend_callback=friend_callback, - order=order, - tournament_id=tournament_id, - score_type=score_type, - campaign=campaign, - level=level) + _bainternal.submit_score( + game=game, + config=config, + name=name, + score=score, + callback=callback, + friend_callback=friend_callback, + order=order, + tournament_id=tournament_id, + score_type=score_type, + campaign=campaign, + level=level, + ) return # This technically breaks since callback will never be called/etc. raise _no_bainternal_error() -def tournament_query(callback: Callable[[dict | None], None], - args: dict) -> None: +def tournament_query( + callback: Callable[[dict | None], None], args: dict +) -> None: """(internal)""" if HAVE_INTERNAL: _bainternal.tournament_query(callback=callback, args=args) @@ -212,8 +222,9 @@ def in_game_purchase(item: str, price: int) -> None: # noinspection PyUnresolvedReferences -def add_transaction(transaction: dict, - callback: Callable | None = None) -> None: +def add_transaction( + transaction: dict, callback: Callable | None = None +) -> None: """(internal)""" if HAVE_INTERNAL: _bainternal.add_transaction(transaction=transaction, callback=callback) @@ -265,7 +276,8 @@ def get_v1_account_misc_read_val(name: str, default_value: Any) -> Any: """(internal)""" if HAVE_INTERNAL: return _bainternal.get_v1_account_misc_read_val( - name=name, default_value=default_value) + name=name, default_value=default_value + ) raise _no_bainternal_error() @@ -273,15 +285,17 @@ def get_v1_account_misc_read_val_2(name: str, default_value: Any) -> Any: """(internal)""" if HAVE_INTERNAL: return _bainternal.get_v1_account_misc_read_val_2( - name=name, default_value=default_value) + name=name, default_value=default_value + ) raise _no_bainternal_error() def get_v1_account_misc_val(name: str, default_value: Any) -> Any: """(internal)""" if HAVE_INTERNAL: - return _bainternal.get_v1_account_misc_val(name=name, - default_value=default_value) + return _bainternal.get_v1_account_misc_val( + name=name, default_value=default_value + ) raise _no_bainternal_error() diff --git a/dist/ba_data/python/ba/_language.py b/dist/ba_data/python/ba/_language.py index dbd740e..9d1fbec 100644 --- a/dist/ba_data/python/ba/_language.py +++ b/dist/ba_data/python/ba/_language.py @@ -35,10 +35,21 @@ class LanguageSubsystem: """ # We don't yet support full unicode display on windows or linux :-(. - if (language in { - 'Chinese', 'ChineseTraditional', 'Persian', 'Korean', 'Arabic', - 'Hindi', 'Vietnamese', 'Thai', 'Tamil' - } and not _ba.can_display_full_unicode()): + if ( + language + in { + 'Chinese', + 'ChineseTraditional', + 'Persian', + 'Korean', + 'Arabic', + 'Hindi', + 'Vietnamese', + 'Thai', + 'Tamil', + } + and not _ba.can_display_full_unicode() + ): return False return True @@ -130,18 +141,22 @@ class LanguageSubsystem: names[i] = 'ChineseTraditional' except Exception: from ba import _error + _error.print_exception() names = [] for name in names: if self._can_display_language(name): langs.add(name) - return sorted(name for name in names - if self._can_display_language(name)) + return sorted( + name for name in names if self._can_display_language(name) + ) - def setlanguage(self, - language: str | None, - print_change: bool = True, - store_to_config: bool = True) -> None: + def setlanguage( + self, + language: str | None, + print_change: bool = True, + store_to_config: bool = True, + ) -> None: """Set the active language used for the game. Pass None to use OS default language. @@ -164,8 +179,9 @@ class LanguageSubsystem: else: switched = False - with open('ba_data/data/languages/english.json', - encoding='utf-8') as infile: + with open( + 'ba_data/data/languages/english.json', encoding='utf-8' + ) as infile: lenglishvalues = json.loads(infile.read()) # None implies default. @@ -175,16 +191,21 @@ class LanguageSubsystem: if language == 'English': lmodvalues = None else: - lmodfile = 'ba_data/data/languages/' + language.lower( - ) + '.json' + lmodfile = ( + 'ba_data/data/languages/' + language.lower() + '.json' + ) with open(lmodfile, encoding='utf-8') as infile: lmodvalues = json.loads(infile.read()) except Exception: from ba import _error + _error.print_exception('Exception importing language:', language) - _ba.screenmessage("Error setting language to '" + language + - "'; see log for details", - color=(1, 0, 0)) + _ba.screenmessage( + "Error setting language to '" + + language + + "'; see log for details", + color=(1, 0, 0), + ) switched = False lmodvalues = None @@ -193,8 +214,8 @@ class LanguageSubsystem: langtarget = self.language_target assert langtarget is not None _add_to_attr_dict( - langtarget, - lmodvalues if lmodvalues is not None else lenglishvalues) + langtarget, lmodvalues if lmodvalues is not None else lenglishvalues + ) # Create an attrdict of our target language overlaid # on our base (english). @@ -209,20 +230,22 @@ class LanguageSubsystem: # Pass some keys/values in for low level code to use; # start with everything in their 'internal' section. internal_vals = [ - v for v in list(lfull['internal'].items()) - if isinstance(v[1], str) + v for v in list(lfull['internal'].items()) if isinstance(v[1], str) ] # Cherry-pick various other values to include. # (should probably get rid of the 'internal' section # and do everything this way) for value in [ - 'replayNameDefaultText', 'replayWriteErrorText', - 'replayVersionErrorText', 'replayReadErrorText' + 'replayNameDefaultText', + 'replayWriteErrorText', + 'replayVersionErrorText', + 'replayReadErrorText', ]: internal_vals.append((value, lfull[value])) internal_vals.append( - ('axisText', lfull['configGamepadWindow']['axisText'])) + ('axisText', lfull['configGamepadWindow']['axisText']) + ) internal_vals.append(('buttonText', lfull['buttonText'])) lmerged = self.language_merged assert lmerged is not None @@ -232,16 +255,22 @@ class LanguageSubsystem: random_names = [n for n in random_names if n != ''] _ba.set_internal_language_keys(internal_vals, random_names) if switched and print_change: - _ba.screenmessage(Lstr(resource='languageSetText', - subs=[('${LANGUAGE}', - Lstr(translate=('languages', - language)))]), - color=(0, 1, 0)) + _ba.screenmessage( + Lstr( + resource='languageSetText', + subs=[ + ('${LANGUAGE}', Lstr(translate=('languages', language))) + ], + ), + color=(0, 1, 0), + ) - def get_resource(self, - resource: str, - fallback_resource: str | None = None, - fallback_value: Any = None) -> Any: + def get_resource( + self, + resource: str, + fallback_resource: str | None = None, + fallback_value: Any = None, + ) -> Any: """Return a translation resource by name. DEPRECATED; use ba.Lstr functionality for these purposes. @@ -251,24 +280,29 @@ class LanguageSubsystem: if self.language_merged is None: language = self.language try: - self.setlanguage(language, - print_change=False, - store_to_config=False) + self.setlanguage( + language, print_change=False, store_to_config=False + ) except Exception: from ba import _error - _error.print_exception('exception setting language to', - language) + + _error.print_exception( + 'exception setting language to', language + ) # Try english as a fallback. if language != 'English': print('Resorting to fallback language (English)') try: - self.setlanguage('English', - print_change=False, - store_to_config=False) + self.setlanguage( + 'English', + print_change=False, + store_to_config=False, + ) except Exception: _error.print_exception( - 'error setting language to english fallback') + 'error setting language to english fallback' + ) # If they provided a fallback_resource value, try the # target-language-only dict first and then fall back to trying the @@ -325,16 +359,20 @@ class LanguageSubsystem: # anywhere. Now if we've been given a fallback value, return it; # otherwise fail. from ba import _error + if fallback_value is not None: return fallback_value raise _error.NotFoundError( - f"Resource not found: '{resource}'") from None + f"Resource not found: '{resource}'" + ) from None - def translate(self, - category: str, - strval: str, - raise_exceptions: bool = False, - print_errors: bool = False) -> str: + def translate( + self, + category: str, + strval: str, + raise_exceptions: bool = False, + print_errors: bool = False, + ) -> str: """Translate a value (or return the value if no translation available) DEPRECATED; use ba.Lstr functionality for these purposes. @@ -345,8 +383,17 @@ class LanguageSubsystem: if raise_exceptions: raise if print_errors: - print(('Translate error: category=\'' + category + - '\' name=\'' + strval + '\' exc=' + str(exc) + '')) + print( + ( + 'Translate error: category=\'' + + category + + '\' name=\'' + + strval + + '\' exc=' + + str(exc) + + '' + ) + ) translated = None translated_out: str if translated is None: @@ -403,28 +450,31 @@ class Lstr: # pylint: disable=dangerous-default-value # noinspection PyDefaultArgument @overload - def __init__(self, - *, - resource: str, - fallback_resource: str = '', - fallback_value: str = '', - subs: Sequence[tuple[str, str | Lstr]] = []) -> None: + def __init__( + self, + *, + resource: str, + fallback_resource: str = '', + fallback_value: str = '', + subs: Sequence[tuple[str, str | Lstr]] = [], + ) -> None: """Create an Lstr from a string resource.""" # noinspection PyShadowingNames,PyDefaultArgument @overload - def __init__(self, - *, - translate: tuple[str, str], - subs: Sequence[tuple[str, str | Lstr]] = []) -> None: + def __init__( + self, + *, + translate: tuple[str, str], + subs: Sequence[tuple[str, str | Lstr]] = [], + ) -> None: """Create an Lstr by translating a string in a category.""" # noinspection PyDefaultArgument @overload - def __init__(self, - *, - value: str, - subs: Sequence[tuple[str, str | Lstr]] = []) -> None: + def __init__( + self, *, value: str, subs: Sequence[tuple[str, str | Lstr]] = [] + ) -> None: """Create an Lstr from a raw string value.""" # pylint: enable=redefined-outer-name, dangerous-default-value @@ -478,10 +528,12 @@ class Lstr: del keywds['value'] if 'fallback' in keywds: from ba import _error + _error.print_error( 'deprecated "fallback" arg passed to Lstr(); use ' 'either "fallback_resource" or "fallback_value"', - once=True) + once=True, + ) keywds['f'] = keywds['fallback'] del keywds['fallback'] if 'fallback_resource' in keywds: @@ -517,6 +569,7 @@ class Lstr: return json.dumps(self.args, separators=(',', ':')) except Exception: from ba import _error + _error.print_exception('_get_json failed for', self.args) return 'JSON_ERR' @@ -542,13 +595,20 @@ def _add_to_attr_dict(dst: AttrDict, src: dict) -> None: except Exception: dst_dict = dst[key] = AttrDict() if not isinstance(dst_dict, AttrDict): - raise RuntimeError("language key '" + key + - "' is defined both as a dict and value") + raise RuntimeError( + "language key '" + + key + + "' is defined both as a dict and value" + ) _add_to_attr_dict(dst_dict, value) else: if not isinstance(value, (float, int, bool, str, str, type(None))): - raise TypeError("invalid value type for res '" + key + "': " + - str(type(value))) + raise TypeError( + "invalid value type for res '" + + key + + "': " + + str(type(value)) + ) dst[key] = value diff --git a/dist/ba_data/python/ba/_level.py b/dist/ba_data/python/ba/_level.py index 124f068..6c1426d 100644 --- a/dist/ba_data/python/ba/_level.py +++ b/dist/ba_data/python/ba/_level.py @@ -20,12 +20,14 @@ class Level: Category: **Gameplay Classes** """ - def __init__(self, - name: str, - gametype: type[ba.GameActivity], - settings: dict, - preview_texture_name: str, - displayname: str | None = None): + def __init__( + self, + name: str, + gametype: type[ba.GameActivity], + settings: dict, + preview_texture_name: str, + displayname: str | None = None, + ): self._name = name self._gametype = gametype self._settings = settings @@ -66,11 +68,18 @@ class Level: def displayname(self) -> ba.Lstr: """The localized name for this Level.""" from ba import _language + return _language.Lstr( - translate=('coopLevelNames', self._displayname - if self._displayname is not None else self._name), - subs=[('${GAME}', - self._gametype.get_display_string(self._settings))]) + translate=( + 'coopLevelNames', + self._displayname + if self._displayname is not None + else self._name, + ), + subs=[ + ('${GAME}', self._gametype.get_display_string(self._settings)) + ], + ) @property def gametype(self) -> type[ba.GameActivity]: @@ -156,10 +165,9 @@ class Level: if campaign is None: raise RuntimeError('Level is not in a campaign.') configdict = campaign.configdict - val: dict[str, Any] = configdict.setdefault(self._name, { - 'Rating': 0.0, - 'Complete': False - }) + val: dict[str, Any] = configdict.setdefault( + self._name, {'Rating': 0.0, 'Complete': False} + ) assert isinstance(val, dict) return val diff --git a/dist/ba_data/python/ba/_lobby.py b/dist/ba_data/python/ba/_lobby.py index 31fcfa5..1bfcfeb 100644 --- a/dist/ba_data/python/ba/_lobby.py +++ b/dist/ba_data/python/ba/_lobby.py @@ -1,6 +1,7 @@ # Released under the MIT License. See LICENSE for details. # """Implements lobby system for gathering before games, char select, etc.""" +# pylint: disable=too-many-lines from __future__ import annotations @@ -31,15 +32,20 @@ class JoinInfo: def __init__(self, lobby: ba.Lobby): from ba._nodeactor import NodeActor from ba._general import WeakCall + self._state = 0 - self._press_to_punch: str | ba.Lstr = ('C' if _ba.app.iircade_mode else - _ba.charstr( - SpecialChar.LEFT_BUTTON)) - self._press_to_bomb: str | ba.Lstr = ('B' if _ba.app.iircade_mode else - _ba.charstr( - SpecialChar.RIGHT_BUTTON)) + self._press_to_punch: str | ba.Lstr = ( + 'C' + if _ba.app.iircade_mode + else _ba.charstr(SpecialChar.LEFT_BUTTON) + ) + self._press_to_bomb: str | ba.Lstr = ( + 'B' + if _ba.app.iircade_mode + else _ba.charstr(SpecialChar.RIGHT_BUTTON) + ) self._joinmsg = Lstr(resource='pressAnyButtonToJoinText') - can_switch_teams = (len(lobby.sessionteams) > 1) + can_switch_teams = len(lobby.sessionteams) > 1 # If we have a keyboard, grab keys for punch and pickup. # FIXME: This of course is only correct on the local device; @@ -50,59 +56,97 @@ class JoinInfo: flatness = 1.0 if _ba.app.vr_mode else 0.0 self._text = NodeActor( - _ba.newnode('text', - attrs={ - 'position': (0, -40), - 'h_attach': 'center', - 'v_attach': 'top', - 'h_align': 'center', - 'color': (0.7, 0.7, 0.95, 1.0), - 'flatness': flatness, - 'text': self._joinmsg - })) + _ba.newnode( + 'text', + attrs={ + 'position': (0, -40), + 'h_attach': 'center', + 'v_attach': 'top', + 'h_align': 'center', + 'color': (0.7, 0.7, 0.95, 1.0), + 'flatness': flatness, + 'text': self._joinmsg, + }, + ) + ) if _ba.app.demo_mode or _ba.app.arcade_mode: self._messages = [self._joinmsg] else: - msg1 = Lstr(resource='pressToSelectProfileText', - subs=[ - ('${BUTTONS}', _ba.charstr(SpecialChar.UP_ARROW) + - ' ' + _ba.charstr(SpecialChar.DOWN_ARROW)) - ]) - msg2 = Lstr(resource='pressToOverrideCharacterText', - subs=[('${BUTTONS}', Lstr(resource='bombBoldText'))]) - msg3 = Lstr(value='${A} < ${B} >', - subs=[('${A}', msg2), ('${B}', self._press_to_bomb)]) - self._messages = (([ - Lstr( - resource='pressToSelectTeamText', - subs=[('${BUTTONS}', _ba.charstr(SpecialChar.LEFT_ARROW) + - ' ' + _ba.charstr(SpecialChar.RIGHT_ARROW))], + msg1 = Lstr( + resource='pressToSelectProfileText', + subs=[ + ( + '${BUTTONS}', + _ba.charstr(SpecialChar.UP_ARROW) + + ' ' + + _ba.charstr(SpecialChar.DOWN_ARROW), + ) + ], + ) + msg2 = Lstr( + resource='pressToOverrideCharacterText', + subs=[('${BUTTONS}', Lstr(resource='bombBoldText'))], + ) + msg3 = Lstr( + value='${A} < ${B} >', + subs=[('${A}', msg2), ('${B}', self._press_to_bomb)], + ) + self._messages = ( + ( + [ + Lstr( + resource='pressToSelectTeamText', + subs=[ + ( + '${BUTTONS}', + _ba.charstr(SpecialChar.LEFT_ARROW) + + ' ' + + _ba.charstr(SpecialChar.RIGHT_ARROW), + ) + ], + ) + ] + if can_switch_teams + else [] ) - ] if can_switch_teams else []) + [msg1] + [msg3] + [self._joinmsg]) + + [msg1] + + [msg3] + + [self._joinmsg] + ) self._timer = _ba.Timer(4.0, WeakCall(self._update), repeat=True) def _update_for_keyboard(self, keyboard: ba.InputDevice) -> None: from ba import _input + punch_key = keyboard.get_button_name( - _input.get_device_value(keyboard, 'buttonPunch')) - self._press_to_punch = Lstr(resource='orText', - subs=[('${A}', - Lstr(value='\'${K}\'', - subs=[('${K}', punch_key)])), - ('${B}', self._press_to_punch)]) + _input.get_device_value(keyboard, 'buttonPunch') + ) + self._press_to_punch = Lstr( + resource='orText', + subs=[ + ('${A}', Lstr(value='\'${K}\'', subs=[('${K}', punch_key)])), + ('${B}', self._press_to_punch), + ], + ) bomb_key = keyboard.get_button_name( - _input.get_device_value(keyboard, 'buttonBomb')) - self._press_to_bomb = Lstr(resource='orText', - subs=[('${A}', - Lstr(value='\'${K}\'', - subs=[('${K}', bomb_key)])), - ('${B}', self._press_to_bomb)]) - self._joinmsg = Lstr(value='${A} < ${B} >', - subs=[('${A}', - Lstr(resource='pressPunchToJoinText')), - ('${B}', self._press_to_punch)]) + _input.get_device_value(keyboard, 'buttonBomb') + ) + self._press_to_bomb = Lstr( + resource='orText', + subs=[ + ('${A}', Lstr(value='\'${K}\'', subs=[('${K}', bomb_key)])), + ('${B}', self._press_to_bomb), + ], + ) + self._joinmsg = Lstr( + value='${A} < ${B} >', + subs=[ + ('${A}', Lstr(resource='pressPunchToJoinText')), + ('${B}', self._press_to_punch), + ], + ) def _update(self) -> None: assert self._text.node @@ -113,12 +157,14 @@ class JoinInfo: @dataclass class PlayerReadyMessage: """Tells an object a player has been selected from the given chooser.""" + chooser: ba.Chooser @dataclass class ChangeMessage: """Tells an object that a selection is being changed.""" + what: str value: int @@ -135,8 +181,9 @@ class Chooser: if self._text_node: self._text_node.delete() - def __init__(self, vpos: float, sessionplayer: _ba.SessionPlayer, - lobby: 'Lobby') -> None: + def __init__( + self, vpos: float, sessionplayer: _ba.SessionPlayer, lobby: 'Lobby' + ) -> None: self._deek_sound = _ba.getsound('deek') self._click_sound = _ba.getsound('click01') self._punchsound = _ba.getsound('punch01') @@ -170,48 +217,54 @@ class Chooser: # for the '_random' profile. Let's use their input_device id to seed # it. This will give a persistent character for them between games # and will distribute characters nicely if everyone is random. - self._random_color, self._random_highlight = ( - get_player_profile_colors(None)) + self._random_color, self._random_highlight = get_player_profile_colors( + None + ) # To calc our random character we pick a random one out of our # unlocked list and then locate that character's index in the full # list. char_index_offset = app.lobby_random_char_index_offset self._random_character_index = ( - (sessionplayer.inputdevice.id + char_index_offset) % - len(self._character_names)) + sessionplayer.inputdevice.id + char_index_offset + ) % len(self._character_names) # Attempt to set an initial profile based on what was used previously # for this input-device, etc. self._profileindex = self._select_initial_profile() self._profilename = self._profilenames[self._profileindex] - self._text_node = _ba.newnode('text', - delegate=self, - attrs={ - 'position': (-100, self._vpos), - 'maxwidth': 160, - 'shadow': 0.5, - 'vr_depth': -20, - 'h_align': 'left', - 'v_align': 'center', - 'v_attach': 'top' - }) + self._text_node = _ba.newnode( + 'text', + delegate=self, + attrs={ + 'position': (-100, self._vpos), + 'maxwidth': 160, + 'shadow': 0.5, + 'vr_depth': -20, + 'h_align': 'left', + 'v_align': 'center', + 'v_attach': 'top', + }, + ) animate(self._text_node, 'scale', {0: 0, 0.1: 1.0}) - self.icon = _ba.newnode('image', - owner=self._text_node, - attrs={ - 'position': (-130, self._vpos + 20), - 'mask_texture': self._mask_texture, - 'vr_depth': -10, - 'attach': 'topCenter' - }) + self.icon = _ba.newnode( + 'image', + owner=self._text_node, + attrs={ + 'position': (-130, self._vpos + 20), + 'mask_texture': self._mask_texture, + 'vr_depth': -10, + 'attach': 'topCenter', + }, + ) animate_array(self.icon, 'scale', 2, {0: (0, 0), 0.1: (45, 45)}) # Set our initial name to '' in case anyone asks. self._sessionplayer.setname( - Lstr(resource='choosingPlayerText').evaluate(), real=False) + Lstr(resource='choosingPlayerText').evaluate(), real=False + ) # Init these to our rando but they should get switched to the # selected profile (if any) right after. @@ -232,32 +285,40 @@ class Chooser: # If we've got a set profile name for this device, work backwards # from that to get our index. - dprofilename = (app.config.get('Default Player Profiles', - {}).get(inputdevice.name + ' ' + - inputdevice.unique_identifier)) + dprofilename = app.config.get('Default Player Profiles', {}).get( + inputdevice.name + ' ' + inputdevice.unique_identifier + ) if dprofilename is not None and dprofilename in profilenames: # If we got '__account__' and its local and we haven't marked # anyone as the 'account profile' device yet, mark this guy as # it. (prevents the next joiner from getting the account # profile too). - if (dprofilename == '__account__' - and not inputdevice.is_remote_client - and app.lobby_account_profile_device_id is None): + if ( + dprofilename == '__account__' + and not inputdevice.is_remote_client + and app.lobby_account_profile_device_id is None + ): app.lobby_account_profile_device_id = inputdevice.id return profilenames.index(dprofilename) # We want to mark the first local input-device in the game # as the 'account profile' device. - if (not inputdevice.is_remote_client - and not inputdevice.is_controller_app): - if (app.lobby_account_profile_device_id is None - and '__account__' in profilenames): + if ( + not inputdevice.is_remote_client + and not inputdevice.is_controller_app + ): + if ( + app.lobby_account_profile_device_id is None + and '__account__' in profilenames + ): app.lobby_account_profile_device_id = inputdevice.id # If this is the designated account-profile-device, try to default # to the account profile. - if (inputdevice.id == app.lobby_account_profile_device_id - and '__account__' in profilenames): + if ( + inputdevice.id == app.lobby_account_profile_device_id + and '__account__' in profilenames + ): return profilenames.index('__account__') # If this is the controller app, it defaults to using a random @@ -274,9 +335,13 @@ class Chooser: # Cycle through our non-random profiles once; after # that, everyone gets random. - while (app.lobby_random_profile_index < len(profilenames) - and profilenames[app.lobby_random_profile_index] - in ('_random', '__account__', '_edit')): + while app.lobby_random_profile_index < len( + profilenames + ) and profilenames[app.lobby_random_profile_index] in ( + '_random', + '__account__', + '_edit', + ): app.lobby_random_profile_index += 1 if app.lobby_random_profile_index < len(profilenames): profileindex = app.lobby_random_profile_index @@ -340,18 +405,22 @@ class Chooser: # character to others they own, but profile characters # should work (and we validate profiles on the master server # so no exploit opportunities) - if (character not in self._character_names - and character in _ba.app.spaz_appearances): + if ( + character not in self._character_names + and character in _ba.app.spaz_appearances + ): self._character_names.append(character) self._character_index = self._character_names.index(character) - self._color, self._highlight = (get_player_profile_colors( - self._profilename, profiles=self._profiles)) + self._color, self._highlight = get_player_profile_colors( + self._profilename, profiles=self._profiles + ) self._update_icon() self._update_text() def reload_profiles(self) -> None: """Reload all player profiles.""" from ba._general import json_prep + app = _ba.app # Re-construct our profile index and other stuff since the profile @@ -396,8 +465,11 @@ class Chooser: # For local devices, add it an 'edit' option which will pop up # the profile window. - if not is_remote and not is_test_input and not (app.demo_mode - or app.arcade_mode): + if ( + not is_remote + and not is_test_input + and not (app.demo_mode or app.arcade_mode) + ): self._profiles['_edit'] = {} # Build a sorted name list we can iterate through. @@ -417,18 +489,25 @@ class Chooser: assert self._text_node spacing = 350 sessionteams = self.lobby.sessionteams - offs = (spacing * -0.5 * len(sessionteams) + - spacing * self._selected_team_index + 250) + offs = ( + spacing * -0.5 * len(sessionteams) + + spacing * self._selected_team_index + + 250 + ) if len(sessionteams) > 1: offs -= 35 - animate_array(self._text_node, 'position', 2, { - 0: self._text_node.position, - 0.1: (-100 + offs, self._vpos + 23) - }) - animate_array(self.icon, 'position', 2, { - 0: self.icon.position, - 0.1: (-130 + offs, self._vpos + 22) - }) + animate_array( + self._text_node, + 'position', + 2, + {0: self._text_node.position, 0.1: (-100 + offs, self._vpos + 23)}, + ) + animate_array( + self.icon, + 'position', + 2, + {0: self.icon.position, 0.1: (-130 + offs, self._vpos + 22)}, + ) def get_character_name(self) -> str: """Return the selected character name.""" @@ -442,16 +521,14 @@ class Chooser: clamp = False if name == '_random': try: - name = ( - self._sessionplayer.inputdevice.get_default_player_name()) + name = self._sessionplayer.inputdevice.get_default_player_name() except Exception: print_exception('Error getting _random chooser name.') name = 'Invalid' clamp = not full elif name == '__account__': try: - name = self._sessionplayer.inputdevice.get_v1_account_name( - full) + name = self._sessionplayer.inputdevice.get_v1_account_name(full) except Exception: print_exception('Error getting account name for chooser.') name = 'Invalid' @@ -459,18 +536,21 @@ class Chooser: elif name == '_edit': # Explicitly flattening this to a str; it's only relevant on # the host so that's ok. - name = (Lstr( + name = Lstr( resource='createEditPlayerText', - fallback_resource='editProfileWindow.titleNewText').evaluate()) + fallback_resource='editProfileWindow.titleNewText', + ).evaluate() else: # If we have a regular profile marked as global with an icon, # use it (for full only). if full: try: if self._profiles[name_raw].get('global', False): - icon = (self._profiles[name_raw]['icon'] - if 'icon' in self._profiles[name_raw] else - _ba.charstr(SpecialChar.LOGO)) + icon = ( + self._profiles[name_raw]['icon'] + if 'icon' in self._profiles[name_raw] + else _ba.charstr(SpecialChar.LOGO) + ) name = icon + name except Exception: print_exception('Error applying global icon.') @@ -488,6 +568,7 @@ class Chooser: # pylint: disable=cyclic-import from bastd.ui.profile import browser as pbrowser from ba._general import Call + profilename = self._profilenames[self._profileindex] # Handle '_edit' as a special case. @@ -503,50 +584,71 @@ class Chooser: if not ready: self._sessionplayer.assigninput( InputType.LEFT_PRESS, - Call(self.handlemessage, ChangeMessage('team', -1))) + Call(self.handlemessage, ChangeMessage('team', -1)), + ) self._sessionplayer.assigninput( InputType.RIGHT_PRESS, - Call(self.handlemessage, ChangeMessage('team', 1))) + Call(self.handlemessage, ChangeMessage('team', 1)), + ) self._sessionplayer.assigninput( InputType.BOMB_PRESS, - Call(self.handlemessage, ChangeMessage('character', 1))) + Call(self.handlemessage, ChangeMessage('character', 1)), + ) self._sessionplayer.assigninput( InputType.UP_PRESS, - Call(self.handlemessage, ChangeMessage('profileindex', -1))) + Call(self.handlemessage, ChangeMessage('profileindex', -1)), + ) self._sessionplayer.assigninput( InputType.DOWN_PRESS, - Call(self.handlemessage, ChangeMessage('profileindex', 1))) + Call(self.handlemessage, ChangeMessage('profileindex', 1)), + ) self._sessionplayer.assigninput( - (InputType.JUMP_PRESS, InputType.PICK_UP_PRESS, - InputType.PUNCH_PRESS), - Call(self.handlemessage, ChangeMessage('ready', 1))) + ( + InputType.JUMP_PRESS, + InputType.PICK_UP_PRESS, + InputType.PUNCH_PRESS, + ), + Call(self.handlemessage, ChangeMessage('ready', 1)), + ) self._ready = False self._update_text() self._sessionplayer.setname('untitled', real=False) else: self._sessionplayer.assigninput( - (InputType.LEFT_PRESS, InputType.RIGHT_PRESS, - InputType.UP_PRESS, InputType.DOWN_PRESS, - InputType.JUMP_PRESS, InputType.BOMB_PRESS, - InputType.PICK_UP_PRESS), self._do_nothing) + ( + InputType.LEFT_PRESS, + InputType.RIGHT_PRESS, + InputType.UP_PRESS, + InputType.DOWN_PRESS, + InputType.JUMP_PRESS, + InputType.BOMB_PRESS, + InputType.PICK_UP_PRESS, + ), + self._do_nothing, + ) self._sessionplayer.assigninput( - (InputType.JUMP_PRESS, InputType.BOMB_PRESS, - InputType.PICK_UP_PRESS, InputType.PUNCH_PRESS), - Call(self.handlemessage, ChangeMessage('ready', 0))) + ( + InputType.JUMP_PRESS, + InputType.BOMB_PRESS, + InputType.PICK_UP_PRESS, + InputType.PUNCH_PRESS, + ), + Call(self.handlemessage, ChangeMessage('ready', 0)), + ) # Store the last profile picked by this input for reuse. input_device = self._sessionplayer.inputdevice name = input_device.name unique_id = input_device.unique_identifier device_profiles = _ba.app.config.setdefault( - 'Default Player Profiles', {}) + 'Default Player Profiles', {} + ) # Make an exception if we have no custom profiles and are set # to random; in that case we'll want to start picking up custom # profiles if/when one is made so keep our setting cleared. special = ('_random', '_edit', '__account__') - have_custom_profiles = any(p not in special - for p in self._profiles) + have_custom_profiles = any(p not in special for p in self._profiles) profilekey = name + ' ' + unique_id if profilename == '_random' and not have_custom_profiles: @@ -557,9 +659,9 @@ class Chooser: _ba.app.config.commit() # Set this player's short and full name. - self._sessionplayer.setname(self._getname(), - self._getname(full=True), - real=True) + self._sessionplayer.setname( + self._getname(), self._getname(full=True), real=True + ) self._ready = True self._update_text() @@ -583,18 +685,21 @@ class Chooser: team_player_counts = {} for sessionteam in sessionteams: team_player_counts[sessionteam.id] = len( - sessionteam.players) + sessionteam.players + ) for chooser in lobby.choosers: if chooser.ready: team_player_counts[chooser.sessionteam.id] += 1 largest_team_size = max(team_player_counts.values()) - smallest_team_size = (min(team_player_counts.values())) + smallest_team_size = min(team_player_counts.values()) # Force switch if we're on the biggest sessionteam # and there's a smaller one available. - if (largest_team_size != smallest_team_size - and team_player_counts[self.sessionteam.id] >= - largest_team_size): + if ( + largest_team_size != smallest_team_size + and team_player_counts[self.sessionteam.id] + >= largest_team_size + ): force_team_switch = True # Either force switch teams, or actually for realsies do the set-ready. @@ -612,8 +717,7 @@ class Chooser: if now - self._last_change[0] < QUICK_CHANGE_INTERVAL: count += 1 if count > MAX_QUICK_CHANGE_COUNT: - _ba.disconnect_client( - self._sessionplayer.inputdevice.client_id) + _ba.disconnect_client(self._sessionplayer.inputdevice.client_id) elif now - self._last_change[0] > QUICK_CHANGE_RESET_INTERVAL: count = 0 self._last_change = (now, count) @@ -638,8 +742,8 @@ class Chooser: if len(sessionteams) > 1: _ba.playsound(self._swish_sound) self._selected_team_index = ( - (self._selected_team_index + msg.value) % - len(sessionteams)) + self._selected_team_index + msg.value + ) % len(sessionteams) self._update_text() self.update_position() self._update_icon() @@ -655,15 +759,17 @@ class Chooser: # Pick the next player profile and assign our name # and character based on that. _ba.playsound(self._deek_sound) - self._profileindex = ((self._profileindex + msg.value) % - len(self._profilenames)) + self._profileindex = (self._profileindex + msg.value) % len( + self._profilenames + ) self.update_from_profile() elif msg.what == 'character': _ba.playsound(self._click_sound) # update our index in our local list of characters - self._character_index = ((self._character_index + msg.value) % - len(self._character_names)) + self._character_index = ( + self._character_index + msg.value + ) % len(self._character_names) self._update_text() self._update_icon() @@ -677,30 +783,34 @@ class Chooser: # Once we're ready, we've saved the name, so lets ask the system # for it so we get appended numbers and stuff. text = Lstr(value=self._sessionplayer.getname(full=True)) - text = Lstr(value='${A} (${B})', - subs=[('${A}', text), - ('${B}', Lstr(resource='readyText'))]) + text = Lstr( + value='${A} (${B})', + subs=[('${A}', text), ('${B}', Lstr(resource='readyText'))], + ) else: text = Lstr(value=self._getname(full=True)) can_switch_teams = len(self.lobby.sessionteams) > 1 # Flash as we're coming in. - fin_color = _ba.safecolor(self.get_color()) + (1, ) + fin_color = _ba.safecolor(self.get_color()) + (1,) if not self._inited: - animate_array(self._text_node, 'color', 4, { - 0.15: fin_color, - 0.25: (2, 2, 2, 1), - 0.35: fin_color - }) + animate_array( + self._text_node, + 'color', + 4, + {0.15: fin_color, 0.25: (2, 2, 2, 1), 0.35: fin_color}, + ) else: # Blend if we're in teams mode; switch instantly otherwise. if can_switch_teams: - animate_array(self._text_node, 'color', 4, { - 0: self._text_node.color, - 0.1: fin_color - }) + animate_array( + self._text_node, + 'color', + 4, + {0: self._text_node.color, 0.1: fin_color}, + ) else: self._text_node.color = fin_color @@ -740,9 +850,11 @@ class Chooser: max_val = sessionteam.color[j] max_index = j that_color_for_us = highlight[max_index] - our_second_biggest = max(highlight[(max_index + 1) % 3], - highlight[(max_index + 2) % 3]) - diff = (that_color_for_us - our_second_biggest) + our_second_biggest = max( + highlight[(max_index + 1) % 3], + highlight[(max_index + 2) % 3], + ) + diff = that_color_for_us - our_second_biggest if diff > 0: highlight[max_index] -= diff * 0.6 highlight[(max_index + 1) % 3] += diff * 0.3 @@ -764,10 +876,12 @@ class Chooser: return try: - tex_name = (_ba.app.spaz_appearances[self._character_names[ - self._character_index]].icon_texture) - tint_tex_name = (_ba.app.spaz_appearances[self._character_names[ - self._character_index]].icon_mask_texture) + tex_name = _ba.app.spaz_appearances[ + self._character_names[self._character_index] + ].icon_texture + tint_tex_name = _ba.app.spaz_appearances[ + self._character_names[self._character_index] + ].icon_mask_texture except Exception: print_exception('Error updating char icon list') tex_name = 'neoSpazIcon' @@ -786,18 +900,18 @@ class Chooser: # If we're initing, flash. if not self._inited: - animate_array(self.icon, 'color', 3, { - 0.15: (1, 1, 1), - 0.25: (2, 2, 2), - 0.35: (1, 1, 1) - }) + animate_array( + self.icon, + 'color', + 3, + {0.15: (1, 1, 1), 0.25: (2, 2, 2), 0.35: (1, 1, 1)}, + ) # Blend in teams mode; switch instantly in ffa-mode. if can_switch_teams: - animate_array(self.icon, 'tint_color', 3, { - 0: self.icon.tint_color, - 0.1: clr - }) + animate_array( + self.icon, 'tint_color', 3, {0: self.icon.tint_color, 0.1: clr} + ) else: self.icon.tint_color = clr self.icon.tint2_color = clr2 @@ -825,6 +939,7 @@ class Lobby: def __init__(self) -> None: from ba._team import SessionTeam from ba._coopsession import CoopSession + session = _ba.getsession() self._use_team_colors = session.use_team_colors if session.use_teams: @@ -834,7 +949,7 @@ class Lobby: else: self._dummy_teams = SessionTeam() self._sessionteams = [weakref.ref(self._dummy_teams)] - v_offset = (-150 if isinstance(session, CoopSession) else -50) + v_offset = -150 if isinstance(session, CoopSession) else -50 self.choosers: list[Chooser] = [] self.base_v_offset = v_offset self.update_positions() @@ -916,9 +1031,11 @@ class Lobby: def add_chooser(self, sessionplayer: ba.SessionPlayer) -> None: """Add a chooser to the lobby for the provided player.""" self.choosers.append( - Chooser(vpos=self._vpos, sessionplayer=sessionplayer, lobby=self)) + Chooser(vpos=self._vpos, sessionplayer=sessionplayer, lobby=self) + ) self._next_add_team = (self._next_add_team + 1) % len( - self._sessionteams) + self._sessionteams + ) self._vpos -= 48 def remove_chooser(self, player: ba.SessionPlayer) -> None: diff --git a/dist/ba_data/python/ba/_map.py b/dist/ba_data/python/ba/_map.py index 2608250..204a0ec 100644 --- a/dist/ba_data/python/ba/_map.py +++ b/dist/ba_data/python/ba/_map.py @@ -49,6 +49,7 @@ def get_map_display_string(name: str) -> ba.Lstr: Category: **Asset Functions** """ from ba import _language + return _language.Lstr(translate=('mapsNames', name)) @@ -97,8 +98,11 @@ def getmaps(playtype: str) -> list[str]: For racing games where players much touch each region in order. Has two or more 'race_point' locations. """ - return sorted(key for key, val in _ba.app.maps.items() - if playtype in val.get_play_types()) + return sorted( + key + for key, val in _ba.app.maps.items() + if playtype in val.get_play_types() + ) def get_map_class(name: str) -> type[ba.Map]: @@ -111,6 +115,7 @@ def get_map_class(name: str) -> type[ba.Map]: return _ba.app.maps[name] except KeyError: from ba import _error + raise _error.NotFoundError(f"Map not found: '{name}'") from None @@ -122,6 +127,7 @@ class Map(Actor): Consists of a collection of terrain nodes, metadata, and other functionality comprising a game map. """ + defs: Any = None name = 'Map' _playtypes: list[str] = [] @@ -170,8 +176,9 @@ class Map(Actor): """ return None - def __init__(self, - vr_overlay_offset: Sequence[float] | None = None) -> None: + def __init__( + self, vr_overlay_offset: Sequence[float] | None = None + ) -> None: """Instantiate a map.""" super().__init__() @@ -185,10 +192,13 @@ class Map(Actor): self.preloaddata = _ba.getactivity().preloads[type(self)] except Exception as exc: from ba import _error + raise _error.NotFoundError( - 'Preload data not found for ' + str(type(self)) + - '; make sure to call the type\'s preload()' - ' staticmethod in the activity constructor') from exc + 'Preload data not found for ' + + str(type(self)) + + '; make sure to call the type\'s preload()' + ' staticmethod in the activity constructor' + ) from exc # Set various globals. gnode = _ba.getactivity().globalsnode @@ -214,9 +224,12 @@ class Map(Actor): # Set shadow ranges. try: gnode.shadow_range = [ - self.defs.points[v][1] for v in [ - 'shadow_lower_bottom', 'shadow_lower_top', - 'shadow_upper_bottom', 'shadow_upper_top' + self.defs.points[v][1] + for v in [ + 'shadow_lower_bottom', + 'shadow_lower_top', + 'shadow_upper_bottom', + 'shadow_upper_top', ] ] except Exception: @@ -224,36 +237,42 @@ class Map(Actor): # In vr, set a fixed point in space for the overlay to show up at. # By default we use the bounds center but allow the map to override it. - center = ((aoi_bounds[0] + aoi_bounds[3]) * 0.5, - (aoi_bounds[1] + aoi_bounds[4]) * 0.5, - (aoi_bounds[2] + aoi_bounds[5]) * 0.5) + center = ( + (aoi_bounds[0] + aoi_bounds[3]) * 0.5, + (aoi_bounds[1] + aoi_bounds[4]) * 0.5, + (aoi_bounds[2] + aoi_bounds[5]) * 0.5, + ) if vr_overlay_offset is not None: - center = (center[0] + vr_overlay_offset[0], - center[1] + vr_overlay_offset[1], - center[2] + vr_overlay_offset[2]) + center = ( + center[0] + vr_overlay_offset[0], + center[1] + vr_overlay_offset[1], + center[2] + vr_overlay_offset[2], + ) gnode.vr_overlay_center = center gnode.vr_overlay_center_enabled = True - self.spawn_points = (self.get_def_points('spawn') - or [(0, 0, 0, 0, 0, 0)]) - self.ffa_spawn_points = (self.get_def_points('ffa_spawn') - or [(0, 0, 0, 0, 0, 0)]) - self.spawn_by_flag_points = (self.get_def_points('spawn_by_flag') - or [(0, 0, 0, 0, 0, 0)]) + self.spawn_points = self.get_def_points('spawn') or [(0, 0, 0, 0, 0, 0)] + self.ffa_spawn_points = self.get_def_points('ffa_spawn') or [ + (0, 0, 0, 0, 0, 0) + ] + self.spawn_by_flag_points = self.get_def_points('spawn_by_flag') or [ + (0, 0, 0, 0, 0, 0) + ] self.flag_points = self.get_def_points('flag') or [(0, 0, 0)] # We just want points. self.flag_points = [p[:3] for p in self.flag_points] - self.flag_points_default = (self.get_def_point('flag_default') - or (0, 1, 0)) + self.flag_points_default = self.get_def_point('flag_default') or ( + 0, + 1, + 0, + ) self.powerup_spawn_points = self.get_def_points('powerup_spawn') or [ (0, 0, 0) ] # We just want points. - self.powerup_spawn_points = ([ - p[:3] for p in self.powerup_spawn_points - ]) + self.powerup_spawn_points = [p[:3] for p in self.powerup_spawn_points] self.tnt_points = self.get_def_points('tnt') or [] # We just want points. @@ -266,11 +285,10 @@ class Map(Actor): # Let's select random index for first spawn point, # so that no one is offended by the constant spawn on the edge. self._next_ffa_start_index = random.randrange( - len(self.ffa_spawn_points)) + len(self.ffa_spawn_points) + ) - def is_point_near_edge(self, - point: ba.Vec3, - running: bool = False) -> bool: + def is_point_near_edge(self, point: ba.Vec3, running: bool = False) -> bool: """Return whether the provided point is near an edge of the map. Simple bot logic uses this call to determine if they @@ -282,22 +300,32 @@ class Map(Actor): return False def get_def_bound_box( - self, name: str + self, name: str ) -> tuple[float, float, float, float, float, float] | None: """Return a 6 member bounds tuple or None if it is not defined.""" try: box = self.defs.boxes[name] - return (box[0] - box[6] / 2.0, box[1] - box[7] / 2.0, - box[2] - box[8] / 2.0, box[0] + box[6] / 2.0, - box[1] + box[7] / 2.0, box[2] + box[8] / 2.0) + return ( + box[0] - box[6] / 2.0, + box[1] - box[7] / 2.0, + box[2] - box[8] / 2.0, + box[0] + box[6] / 2.0, + box[1] + box[7] / 2.0, + box[2] + box[8] / 2.0, + ) except Exception: return None def get_def_point(self, name: str) -> Sequence[float] | None: """Return a single defined point or a default value in its absence.""" val = self.defs.points.get(name) - return (None if val is None else - _math.vec3validate(val) if __debug__ else val) + return ( + None + if val is None + else _math.vec3validate(val) + if __debug__ + else val + ) def get_def_points(self, name: str) -> list[Sequence[float]]: """Return a list of named points. @@ -324,12 +352,16 @@ class Map(Actor): pnt = self.spawn_points[team_index % len(self.spawn_points)] x_range = (-0.5, 0.5) if pnt[3] == 0.0 else (-pnt[3], pnt[3]) z_range = (-0.5, 0.5) if pnt[5] == 0.0 else (-pnt[5], pnt[5]) - pnt = (pnt[0] + random.uniform(*x_range), pnt[1], - pnt[2] + random.uniform(*z_range)) + pnt = ( + pnt[0] + random.uniform(*x_range), + pnt[1], + pnt[2] + random.uniform(*z_range), + ) return pnt def get_ffa_start_position( - self, players: Sequence[ba.Player]) -> Sequence[float]: + self, players: Sequence[ba.Player] + ) -> Sequence[float]: """Return a random starting position in one of the FFA spawn areas. If a list of ba.Player-s is provided; the returned points will be @@ -344,12 +376,16 @@ class Map(Actor): def _getpt() -> Sequence[float]: point = self.ffa_spawn_points[self._next_ffa_start_index] - self._next_ffa_start_index = ((self._next_ffa_start_index + 1) % - len(self.ffa_spawn_points)) + self._next_ffa_start_index = (self._next_ffa_start_index + 1) % len( + self.ffa_spawn_points + ) x_range = (-0.5, 0.5) if point[3] == 0.0 else (-point[3], point[3]) z_range = (-0.5, 0.5) if point[5] == 0.0 else (-point[5], point[5]) - point = (point[0] + random.uniform(*x_range), point[1], - point[2] + random.uniform(*z_range)) + point = ( + point[0] + random.uniform(*x_range), + point[1], + point[2] + random.uniform(*z_range), + ) return point if not player_pts: @@ -372,8 +408,9 @@ class Map(Actor): assert farthestpt is not None return tuple(farthestpt) - def get_flag_position(self, - team_index: int | None = None) -> Sequence[float]: + def get_flag_position( + self, team_index: int | None = None + ) -> Sequence[float]: """Return a flag position on the map for the given team index. Pass None to get the default flag point. @@ -388,6 +425,7 @@ class Map(Actor): def handlemessage(self, msg: Any) -> Any: from ba import _messages + if isinstance(msg, _messages.DieMessage): if self.node: self.node.delete() diff --git a/dist/ba_data/python/ba/_math.py b/dist/ba_data/python/ba/_math.py index 856d426..90ce3cb 100644 --- a/dist/ba_data/python/ba/_math.py +++ b/dist/ba_data/python/ba/_math.py @@ -24,6 +24,7 @@ def vec3validate(value: Sequence[float]) -> Sequence[float]: to keep runtime overhead minimal. """ from numbers import Number + if not isinstance(value, abc.Sequence): raise TypeError(f"Expected a sequence; got {type(value)}") if len(value) != 3: @@ -40,9 +41,11 @@ def is_point_in_box(pnt: Sequence[float], box: Sequence[float]) -> bool: For use with standard def boxes (position|rotate|scale). """ - return ((abs(pnt[0] - box[0]) <= box[6] * 0.5) - and (abs(pnt[1] - box[1]) <= box[7] * 0.5) - and (abs(pnt[2] - box[2]) <= box[8] * 0.5)) + return ( + (abs(pnt[0] - box[0]) <= box[6] * 0.5) + and (abs(pnt[1] - box[1]) <= box[7] * 0.5) + and (abs(pnt[2] - box[2]) <= box[8] * 0.5) + ) def normalized_color(color: Sequence[float]) -> tuple[float, ...]: diff --git a/dist/ba_data/python/ba/_messages.py b/dist/ba_data/python/ba/_messages.py index 1ea794c..df2701b 100644 --- a/dist/ba_data/python/ba/_messages.py +++ b/dist/ba_data/python/ba/_messages.py @@ -38,6 +38,7 @@ class DeathType(Enum): Category: Enums """ + GENERIC = 'generic' OUT_OF_BOUNDS = 'out_of_bounds' IMPACT = 'impact' @@ -83,8 +84,13 @@ class PlayerDiedMessage: how: ba.DeathType """The particular type of death.""" - def __init__(self, player: ba.Player, was_killed: bool, - killerplayer: ba.Player | None, how: ba.DeathType): + def __init__( + self, + player: ba.Player, + was_killed: bool, + killerplayer: ba.Player | None, + how: ba.DeathType, + ): """Instantiate a message with the given values.""" # Invalid refs should never be passed as args. @@ -97,8 +103,9 @@ class PlayerDiedMessage: self.killed = was_killed self.how = how - def getkillerplayer(self, - playertype: type[PlayerType]) -> PlayerType | None: + def getkillerplayer( + self, playertype: type[PlayerType] + ) -> PlayerType | None: """Return the ba.Player responsible for the killing, if any. Pass the Player type being used by the current game. @@ -235,19 +242,21 @@ class HitMessage: their effect to a target. """ - def __init__(self, - srcnode: ba.Node | None = None, - pos: Sequence[float] | None = None, - velocity: Sequence[float] | None = None, - magnitude: float = 1.0, - velocity_magnitude: float = 0.0, - radius: float = 1.0, - source_player: ba.Player | None = None, - kick_back: float = 1.0, - flat_damage: float | None = None, - hit_type: str = 'generic', - force_direction: Sequence[float] | None = None, - hit_subtype: str = 'default'): + def __init__( + self, + srcnode: ba.Node | None = None, + pos: Sequence[float] | None = None, + velocity: Sequence[float] | None = None, + magnitude: float = 1.0, + velocity_magnitude: float = 0.0, + radius: float = 1.0, + source_player: ba.Player | None = None, + kick_back: float = 1.0, + flat_damage: float | None = None, + hit_type: str = 'generic', + force_direction: Sequence[float] | None = None, + hit_subtype: str = 'default', + ): """Instantiate a message with given values.""" self.srcnode = srcnode @@ -264,11 +273,13 @@ class HitMessage: self.flat_damage = flat_damage self.hit_type = hit_type self.hit_subtype = hit_subtype - self.force_direction = (force_direction - if force_direction is not None else velocity) + self.force_direction = ( + force_direction if force_direction is not None else velocity + ) - def get_source_player(self, - playertype: type[PlayerType]) -> PlayerType | None: + def get_source_player( + self, playertype: type[PlayerType] + ) -> PlayerType | None: """Return the source-player if one exists and is the provided type.""" player: Any = self._source_player diff --git a/dist/ba_data/python/ba/_meta.py b/dist/ba_data/python/ba/_meta.py index 7b43f9b..dd62e8d 100644 --- a/dist/ba_data/python/ba/_meta.py +++ b/dist/ba_data/python/ba/_meta.py @@ -43,6 +43,7 @@ T = TypeVar('T') @dataclass class ScanResults: """Final results from a meta-scan.""" + exports: dict[str, list[str]] = field(default_factory=dict) errors: list[str] = field(default_factory=list) warnings: list[str] = field(default_factory=list) @@ -84,7 +85,8 @@ class MetadataSubsystem: self._scan_complete_cb = scan_complete_cb self._scan = DirectoryScan( - [_ba.app.python_directory_app, _ba.app.python_directory_user]) + [_ba.app.python_directory_app, _ba.app.python_directory_user] + ) Thread(target=self._run_scan_in_bg, daemon=True).start() @@ -115,8 +117,12 @@ class MetadataSubsystem: loading work happens, pass completion_cb_in_bg_thread=True. """ Thread( - target=tpartial(self._load_exported_classes, cls, completion_cb, - completion_cb_in_bg_thread), + target=tpartial( + self._load_exported_classes, + cls, + completion_cb, + completion_cb_in_bg_thread, + ), daemon=True, ).start() @@ -127,6 +133,7 @@ class MetadataSubsystem: completion_cb_in_bg_thread: bool, ) -> None: from ba._general import getclass + classes: list[type[T]] = [] try: classnames = self._wait_for_scan_results().exports_of_class(cls) @@ -152,7 +159,8 @@ class MetadataSubsystem: logging.warning( 'ba.meta._wait_for_scan_results()' ' called in logic thread before scan completed;' - ' this can cause hitches.') + ' this can cause hitches.' + ) # Now wait a bit for the scan to complete. # Eventually error though if it doesn't. @@ -161,7 +169,8 @@ class MetadataSubsystem: time.sleep(0.05) if time.time() - starttime > 10.0: raise TimeoutError( - 'timeout waiting for meta scan to complete.') + 'timeout waiting for meta scan to complete.' + ) return self.scanresults def _run_scan_in_bg(self) -> None: @@ -181,6 +190,7 @@ class MetadataSubsystem: def _handle_scan_results(self) -> None: """Called in the logic thread with results of a completed scan.""" from ba._language import Lstr + assert _ba.in_logic_thread() results = self.scanresults @@ -192,16 +202,20 @@ class MetadataSubsystem: # Errors are more serious and will get included in the regular log. if results.warnings or results.errors: import textwrap - _ba.screenmessage(Lstr(resource='scanScriptsErrorText'), - color=(1, 0, 0)) + + _ba.screenmessage( + Lstr(resource='scanScriptsErrorText'), color=(1, 0, 0) + ) _ba.playsound(_ba.getsound('error')) if results.warnings: - allwarnings = textwrap.indent('\n'.join(results.warnings), - 'Warning (meta-scan): ') + allwarnings = textwrap.indent( + '\n'.join(results.warnings), 'Warning (meta-scan): ' + ) logging.warning(allwarnings) if results.errors: - allerrors = textwrap.indent('\n'.join(results.errors), - 'Error (meta-scan): ') + allerrors = textwrap.indent( + '\n'.join(results.errors), 'Error (meta-scan): ' + ) logging.error(allerrors) # Let the game know we're done. @@ -248,23 +262,28 @@ class DirectoryScan: self._scan_module(moduledir, subpath) except Exception: import traceback + self.results.warnings.append( - f"Error scanning '{subpath}': " + - traceback.format_exc()) + f"Error scanning '{subpath}': " + traceback.format_exc() + ) # Sort our results for exportlist in self.results.exports.values(): exportlist.sort() - def _get_path_module_entries(self, path: Path, subpath: str | Path, - modules: list[tuple[Path, Path]]) -> None: + def _get_path_module_entries( + self, path: Path, subpath: str | Path, modules: list[tuple[Path, Path]] + ) -> None: """Scan provided path and add module entries to provided list.""" try: # Special case: let's save some time and skip the whole 'ba' # package since we know it doesn't contain any meta tags. fullpath = Path(path, subpath) - entries = [(path, Path(subpath, name)) - for name in os.listdir(fullpath) if name != 'ba'] + entries = [ + (path, Path(subpath, name)) + for name in os.listdir(fullpath) + if name != 'ba' + ] except PermissionError: # Expected sometimes. entries = [] @@ -277,8 +296,10 @@ class DirectoryScan: for entry in entries: if entry[1].name.endswith('.py'): modules.append(entry) - elif (Path(entry[0], entry[1]).is_dir() - and Path(entry[0], entry[1], '__init__.py').is_file()): + elif ( + Path(entry[0], entry[1]).is_dir() + and Path(entry[0], entry[1], '__init__.py').is_file() + ): modules.append(entry) def _scan_module(self, moduledir: Path, subpath: Path) -> None: @@ -293,11 +314,13 @@ class DirectoryScan: flines = infile.readlines() meta_lines = { lnum: l[1:].split() - for lnum, l in enumerate(flines) if '# ba_meta ' in l + for lnum, l in enumerate(flines) + if '# ba_meta ' in l } is_top_level = len(subpath.parts) <= 1 - required_api = self._get_api_requirement(subpath, meta_lines, - is_top_level) + required_api = self._get_api_requirement( + subpath, meta_lines, is_top_level + ) # Top level modules with no discernible api version get ignored. if is_top_level and required_api is None: @@ -308,7 +331,8 @@ class DirectoryScan: if required_api is not None and required_api < CURRENT_API_VERSION: self.results.warnings += ( f'Warning: {subpath} requires api {required_api} but' - f' we are running {CURRENT_API_VERSION}; ignoring module.') + f' we are running {CURRENT_API_VERSION}; ignoring module.' + ) return # Ok; can proceed with a full scan of this module. @@ -324,11 +348,14 @@ class DirectoryScan: self._scan_module(submodule[0], submodule[1]) except Exception: import traceback - self.results.warnings.append( - f"Error scanning '{subpath}': {traceback.format_exc()}") - def _process_module_meta_tags(self, subpath: Path, flines: list[str], - meta_lines: dict[int, list[str]]) -> None: + self.results.warnings.append( + f"Error scanning '{subpath}': {traceback.format_exc()}" + ) + + def _process_module_meta_tags( + self, subpath: Path, flines: list[str], meta_lines: dict[int, list[str]] + ) -> None: """Pull data from a module based on its ba_meta tags.""" for lindex, mline in meta_lines.items(): # meta_lines is just anything containing '# ba_meta '; make sure @@ -336,9 +363,11 @@ class DirectoryScan: if mline[0] != 'ba_meta': self.results.warnings.append( f'Warning: {subpath}:' - f' malformed ba_meta statement on line {lindex + 1}.') - elif (len(mline) == 4 and mline[1] == 'require' - and mline[2] == 'api'): + f' malformed ba_meta statement on line {lindex + 1}.' + ) + elif ( + len(mline) == 4 and mline[1] == 'require' and mline[2] == 'api' + ): # Ignore 'require api X' lines in this pass. pass elif len(mline) != 3 or mline[1] != 'export': @@ -346,7 +375,8 @@ class DirectoryScan: # complain for anything else we see. self.results.warnings.append( f'Warning: {subpath}' - f': unrecognized ba_meta statement on line {lindex + 1}.') + f': unrecognized ba_meta statement on line {lindex + 1}.' + ) else: # Looks like we've got a valid export line! modulename = '.'.join(subpath.parts) @@ -354,7 +384,8 @@ class DirectoryScan: modulename = modulename[:-3] exporttypestr = mline[2] export_class_name = self._get_export_class_name( - subpath, flines, lindex) + subpath, flines, lindex + ) if export_class_name is not None: classname = modulename + '.' + export_class_name @@ -364,11 +395,13 @@ class DirectoryScan: exporttype = EXPORT_CLASS_NAME_SHORTCUTS.get(exporttypestr) if exporttype is None: exporttype = exporttypestr - self.results.exports.setdefault(exporttype, - []).append(classname) + self.results.exports.setdefault(exporttype, []).append( + classname + ) - def _get_export_class_name(self, subpath: Path, lines: list[str], - lindex: int) -> str | None: + def _get_export_class_name( + self, subpath: Path, lines: list[str], lindex: int + ) -> str | None: """Given line num of an export tag, returns its operand class name.""" lindexorig = lindex classname = None @@ -389,7 +422,8 @@ class DirectoryScan: if classname is None: self.results.warnings.append( f'Warning: {subpath}: class definition not found below' - f' "ba_meta export" statement on line {lindexorig + 1}.') + f' "ba_meta export" statement on line {lindexorig + 1}.' + ) return classname def _get_api_requirement( @@ -403,8 +437,13 @@ class DirectoryScan: Malformed api requirement strings will be logged as warnings. """ lines = [ - l for l in meta_lines.values() if len(l) == 4 and l[0] == 'ba_meta' - and l[1] == 'require' and l[2] == 'api' and l[3].isdigit() + l + for l in meta_lines.values() + if len(l) == 4 + and l[0] == 'ba_meta' + and l[1] == 'require' + and l[2] == 'api' + and l[3].isdigit() ] # We're successful if we find exactly one properly formatted line. @@ -416,12 +455,14 @@ class DirectoryScan: self.results.warnings.append( f'Warning: {subpath}: multiple' ' "# ba_meta require api " lines found;' - ' ignoring module.') + ' ignoring module.' + ) elif not lines and toplevel and meta_lines: # If we're a top-level module containing meta lines but # no valid "require api" line found, complain. self.results.warnings.append( f'Warning: {subpath}:' ' no valid "# ba_meta require api " line found;' - ' ignoring module.') + ' ignoring module.' + ) return None diff --git a/dist/ba_data/python/ba/_multiteamsession.py b/dist/ba_data/python/ba/_multiteamsession.py index cfe4a7c..d653ff9 100644 --- a/dist/ba_data/python/ba/_multiteamsession.py +++ b/dist/ba_data/python/ba/_multiteamsession.py @@ -38,6 +38,7 @@ class MultiTeamSession(Session): # pylint: disable=cyclic-import from ba import _playlist from bastd.activity.multiteamjoin import MultiTeamJoinActivity + app = _ba.app cfg = app.config @@ -51,11 +52,13 @@ class MultiTeamSession(Session): # print('FIXME: TEAM BASE SESSION WOULD CALC DEPS.') depsets: Sequence[ba.DependencySet] = [] - super().__init__(depsets, - team_names=team_names, - team_colors=team_colors, - min_players=1, - max_players=self.get_max_players()) + super().__init__( + depsets, + team_names=team_names, + team_colors=team_colors, + min_players=1, + max_players=self.get_max_players(), + ) self._series_length = app.teams_series_length self._ffa_series_length = app.ffa_series_length @@ -67,13 +70,13 @@ class MultiTeamSession(Session): from bastd.tutorial import TutorialActivity # Get this loading. - self._tutorial_activity_instance = _ba.newactivity( - TutorialActivity) + self._tutorial_activity_instance = _ba.newactivity(TutorialActivity) else: self._tutorial_activity_instance = None - self._playlist_name = cfg.get(self._playlist_selection_var, - '__default__') + self._playlist_name = cfg.get( + self._playlist_selection_var, '__default__' + ) self._playlist_randomize = cfg.get(self._playlist_randomize_var, False) # Which game activity we're on. @@ -81,8 +84,10 @@ class MultiTeamSession(Session): playlists = cfg.get(self._playlists_var, {}) - if (self._playlist_name != '__default__' - and self._playlist_name in playlists): + if ( + self._playlist_name != '__default__' + and self._playlist_name in playlists + ): # Make sure to copy this, as we muck with it in place once we've # got it and we don't want that to affect our config. @@ -98,19 +103,22 @@ class MultiTeamSession(Session): playlist, sessiontype=type(self), add_resolved_type=True, - name='default teams' if self.use_teams else 'default ffa') + name='default teams' if self.use_teams else 'default ffa', + ) if not playlist_resolved: raise RuntimeError('Playlist contains no valid games.') - self._playlist = ShuffleList(playlist_resolved, - shuffle=self._playlist_randomize) + self._playlist = ShuffleList( + playlist_resolved, shuffle=self._playlist_randomize + ) # Get a game on deck ready to go. self._current_game_spec: dict[str, Any] | None = None self._next_game_spec: dict[str, Any] = self._playlist.pull_next() - self._next_game: type[ba.GameActivity] = ( - self._next_game_spec['resolved_type']) + self._next_game: type[ba.GameActivity] = self._next_game_spec[ + 'resolved_type' + ] # Go ahead and instantiate the next game we'll # use so it has lots of time to load. @@ -131,6 +139,7 @@ class MultiTeamSession(Session): """Returns a description of the next game on deck.""" # pylint: disable=cyclic-import from ba._gameactivity import GameActivity + gametype: type[GameActivity] = self._next_game_spec['resolved_type'] assert issubclass(gametype, GameActivity) return gametype.get_settings_display_string(self._next_game_spec) @@ -151,15 +160,20 @@ class MultiTeamSession(Session): def _instantiate_next_game(self) -> None: self._next_game_instance = _ba.newactivity( self._next_game_spec['resolved_type'], - self._next_game_spec['settings']) + self._next_game_spec['settings'], + ) def on_activity_end(self, activity: ba.Activity, results: Any) -> None: # pylint: disable=cyclic-import from bastd.tutorial import TutorialActivity from bastd.activity.multiteamvictory import ( - TeamSeriesVictoryScoreScreenActivity) - from ba._activitytypes import (TransitionActivity, JoinActivity, - ScoreScreenActivity) + TeamSeriesVictoryScoreScreenActivity, + ) + from ba._activitytypes import ( + TransitionActivity, + JoinActivity, + ScoreScreenActivity, + ) # If we have a tutorial to show, that's the first thing we do no # matter what. @@ -176,8 +190,8 @@ class MultiTeamSession(Session): # If we're in a between-round activity or a restart-activity, hop # into a round. elif isinstance( - activity, - (JoinActivity, TransitionActivity, ScoreScreenActivity)): + activity, (JoinActivity, TransitionActivity, ScoreScreenActivity) + ): # If we're coming from a series-end activity, reset scores. if isinstance(activity, TeamSeriesVictoryScoreScreenActivity): @@ -204,7 +218,7 @@ class MultiTeamSession(Session): # ..but only ones who have been placed on a team # (ie: no longer sitting in the lobby). try: - has_team = (player.sessionteam is not None) + has_team = player.sessionteam is not None except NotFoundError: has_team = False if has_team: @@ -223,11 +237,13 @@ class MultiTeamSession(Session): del results # Unused arg. print_error('this should be overridden') - def announce_game_results(self, - activity: ba.GameActivity, - results: ba.GameResults, - delay: float, - announce_winning_team: bool = True) -> None: + def announce_game_results( + self, + activity: ba.GameActivity, + results: ba.GameResults, + delay: float, + announce_winning_team: bool = True, + ) -> None: """Show basic game result at the end of a game. (before transitioning to a score screen). @@ -243,6 +259,7 @@ class MultiTeamSession(Session): from ba._language import Lstr from ba._freeforallsession import FreeForAllSession from ba._messages import CelebrateMessage + _ba.timer(delay, Call(_ba.playsound, _ba.getsound('boxingBell'))) if announce_winning_team: @@ -261,8 +278,10 @@ class MultiTeamSession(Session): wins_resource = 'winsPlayerText' else: wins_resource = 'winsTeamText' - wins_text = Lstr(resource=wins_resource, - subs=[('${NAME}', winning_sessionteam.name)]) + wins_text = Lstr( + resource=wins_resource, + subs=[('${NAME}', winning_sessionteam.name)], + ) activity.show_zoom_message( wins_text, scale=0.85, @@ -300,8 +319,10 @@ class ShuffleList: # If the new one is the same map or game-type as the previous, # lets try to keep looking. if len(self.shuffle_list) > 1 and self.last_gotten is not None: - if (test_obj['settings']['map'] == - self.last_gotten['settings']['map']): + if ( + test_obj['settings']['map'] + == self.last_gotten['settings']['map'] + ): continue if test_obj['type'] == self.last_gotten['type']: continue diff --git a/dist/ba_data/python/ba/_music.py b/dist/ba_data/python/ba/_music.py index 4940e4c..79693c4 100644 --- a/dist/ba_data/python/ba/_music.py +++ b/dist/ba_data/python/ba/_music.py @@ -24,6 +24,7 @@ class MusicType(Enum): 'situations'. The actual music played for each type can be overridden by the game or by the user. """ + MENU = 'Menu' VICTORY = 'Victory' CHAR_SELECT = 'CharSelect' @@ -53,6 +54,7 @@ class MusicPlayMode(Enum): Category: **Enums** """ + REGULAR = 'regular' TEST = 'test' @@ -63,6 +65,7 @@ class AssetSoundtrackEntry: Category: **App Classes** """ + assetname: str volume: float = 1.0 loop: bool = True @@ -70,50 +73,38 @@ class AssetSoundtrackEntry: # What gets played by default for our different music types: ASSET_SOUNDTRACK_ENTRIES: dict[MusicType, AssetSoundtrackEntry] = { - MusicType.MENU: - AssetSoundtrackEntry('menuMusic'), - MusicType.VICTORY: - AssetSoundtrackEntry('victoryMusic', volume=1.2, loop=False), - MusicType.CHAR_SELECT: - AssetSoundtrackEntry('charSelectMusic', volume=0.4), - MusicType.RUN_AWAY: - AssetSoundtrackEntry('runAwayMusic', volume=1.2), - MusicType.ONSLAUGHT: - AssetSoundtrackEntry('runAwayMusic', volume=1.2), - MusicType.KEEP_AWAY: - AssetSoundtrackEntry('runAwayMusic', volume=1.2), - MusicType.RACE: - AssetSoundtrackEntry('runAwayMusic', volume=1.2), - MusicType.EPIC_RACE: - AssetSoundtrackEntry('slowEpicMusic', volume=1.2), - MusicType.SCORES: - AssetSoundtrackEntry('scoresEpicMusic', volume=0.6, loop=False), - MusicType.GRAND_ROMP: - AssetSoundtrackEntry('grandRompMusic', volume=1.2), - MusicType.TO_THE_DEATH: - AssetSoundtrackEntry('toTheDeathMusic', volume=1.2), - MusicType.CHOSEN_ONE: - AssetSoundtrackEntry('survivalMusic', volume=0.8), - MusicType.FORWARD_MARCH: - AssetSoundtrackEntry('forwardMarchMusic', volume=0.8), - MusicType.FLAG_CATCHER: - AssetSoundtrackEntry('flagCatcherMusic', volume=1.2), - MusicType.SURVIVAL: - AssetSoundtrackEntry('survivalMusic', volume=0.8), - MusicType.EPIC: - AssetSoundtrackEntry('slowEpicMusic', volume=1.2), - MusicType.SPORTS: - AssetSoundtrackEntry('sportsMusic', volume=0.8), - MusicType.HOCKEY: - AssetSoundtrackEntry('sportsMusic', volume=0.8), - MusicType.FOOTBALL: - AssetSoundtrackEntry('sportsMusic', volume=0.8), - MusicType.FLYING: - AssetSoundtrackEntry('flyingMusic', volume=0.8), - MusicType.SCARY: - AssetSoundtrackEntry('scaryMusic', volume=0.8), - MusicType.MARCHING: - AssetSoundtrackEntry('whenJohnnyComesMarchingHomeMusic', volume=0.8), + MusicType.MENU: AssetSoundtrackEntry('menuMusic'), + MusicType.VICTORY: AssetSoundtrackEntry( + 'victoryMusic', volume=1.2, loop=False + ), + MusicType.CHAR_SELECT: AssetSoundtrackEntry('charSelectMusic', volume=0.4), + MusicType.RUN_AWAY: AssetSoundtrackEntry('runAwayMusic', volume=1.2), + MusicType.ONSLAUGHT: AssetSoundtrackEntry('runAwayMusic', volume=1.2), + MusicType.KEEP_AWAY: AssetSoundtrackEntry('runAwayMusic', volume=1.2), + MusicType.RACE: AssetSoundtrackEntry('runAwayMusic', volume=1.2), + MusicType.EPIC_RACE: AssetSoundtrackEntry('slowEpicMusic', volume=1.2), + MusicType.SCORES: AssetSoundtrackEntry( + 'scoresEpicMusic', volume=0.6, loop=False + ), + MusicType.GRAND_ROMP: AssetSoundtrackEntry('grandRompMusic', volume=1.2), + MusicType.TO_THE_DEATH: AssetSoundtrackEntry('toTheDeathMusic', volume=1.2), + MusicType.CHOSEN_ONE: AssetSoundtrackEntry('survivalMusic', volume=0.8), + MusicType.FORWARD_MARCH: AssetSoundtrackEntry( + 'forwardMarchMusic', volume=0.8 + ), + MusicType.FLAG_CATCHER: AssetSoundtrackEntry( + 'flagCatcherMusic', volume=1.2 + ), + MusicType.SURVIVAL: AssetSoundtrackEntry('survivalMusic', volume=0.8), + MusicType.EPIC: AssetSoundtrackEntry('slowEpicMusic', volume=1.2), + MusicType.SPORTS: AssetSoundtrackEntry('sportsMusic', volume=0.8), + MusicType.HOCKEY: AssetSoundtrackEntry('sportsMusic', volume=0.8), + MusicType.FOOTBALL: AssetSoundtrackEntry('sportsMusic', volume=0.8), + MusicType.FLYING: AssetSoundtrackEntry('flyingMusic', volume=0.8), + MusicType.SCARY: AssetSoundtrackEntry('scaryMusic', volume=0.8), + MusicType.MARCHING: AssetSoundtrackEntry( + 'whenJohnnyComesMarchingHomeMusic', volume=0.8 + ), } @@ -133,7 +124,7 @@ class MusicSubsystem: self._music_player_type: type[MusicPlayer] | None = None self.music_types: dict[MusicPlayMode, MusicType | None] = { MusicPlayMode.REGULAR: None, - MusicPlayMode.TEST: None + MusicPlayMode.TEST: None, } # Set up custom music players for platforms that support them. @@ -143,9 +134,11 @@ class MusicSubsystem: # instead of a special case. if self.supports_soundtrack_entry_type('musicFile'): from ba.osmusic import OSMusicPlayer + self._music_player_type = OSMusicPlayer elif self.supports_soundtrack_entry_type('iTunesPlaylist'): from ba.macmusicapp import MacMusicAppMusicPlayer + self._music_player_type = MacMusicAppMusicPlayer def on_app_launch(self) -> None: @@ -156,11 +149,14 @@ class MusicSubsystem: # out than later). try: cfg = _ba.app.config - if ('Soundtrack' in cfg and cfg['Soundtrack'] - not in ['__default__', 'Default Soundtrack']): + if 'Soundtrack' in cfg and cfg['Soundtrack'] not in [ + '__default__', + 'Default Soundtrack', + ]: self.get_music_player() except Exception: from ba import _error + _error.print_exception('error prepping music-player') def on_app_shutdown(self) -> None: @@ -185,9 +181,9 @@ class MusicSubsystem: if self._music_player is not None: self._music_player.set_volume(val) - def set_music_play_mode(self, - mode: MusicPlayMode, - force_restart: bool = False) -> None: + def set_music_play_mode( + self, mode: MusicPlayMode, force_restart: bool = False + ) -> None: """Sets music play mode; used for soundtrack testing/etc.""" old_mode = self._music_mode self._music_mode = mode @@ -210,8 +206,10 @@ class MusicSubsystem: if entry_type == 'iTunesPlaylist': return 'Mac' in uas if entry_type in ('musicFile', 'musicFolder'): - return ('android' in uas - and _ba.android_get_external_files_dir() is not None) + return ( + 'android' in uas + and _ba.android_get_external_files_dir() is not None + ) if entry_type == 'default': return True return False @@ -228,18 +226,28 @@ class MusicSubsystem: entry_type = 'iTunesPlaylist' # For other entries we expect type and name strings in a dict. - elif (isinstance(entry, dict) and 'type' in entry - and isinstance(entry['type'], str) and 'name' in entry - and isinstance(entry['name'], str)): + elif ( + isinstance(entry, dict) + and 'type' in entry + and isinstance(entry['type'], str) + and 'name' in entry + and isinstance(entry['name'], str) + ): entry_type = entry['type'] else: - raise TypeError('invalid soundtrack entry: ' + str(entry) + - ' (type ' + str(type(entry)) + ')') + raise TypeError( + 'invalid soundtrack entry: ' + + str(entry) + + ' (type ' + + str(type(entry)) + + ')' + ) if self.supports_soundtrack_entry_type(entry_type): return entry_type raise ValueError('invalid soundtrack entry:' + str(entry)) except Exception: from ba import _error + _error.print_exception() return 'default' @@ -254,13 +262,18 @@ class MusicSubsystem: return entry # For other entries we expect type and name strings in a dict. - if (isinstance(entry, dict) and 'type' in entry - and isinstance(entry['type'], str) and 'name' in entry - and isinstance(entry['name'], str)): + if ( + isinstance(entry, dict) + and 'type' in entry + and isinstance(entry['type'], str) + and 'name' in entry + and isinstance(entry['name'], str) + ): return entry['name'] raise ValueError('invalid soundtrack entry:' + str(entry)) except Exception: from ba import _error + _error.print_exception() return 'default' @@ -269,11 +282,13 @@ class MusicSubsystem: if _ba.is_os_playing_music(): self.do_play_music(None) - def do_play_music(self, - musictype: MusicType | str | None, - continuous: bool = False, - mode: MusicPlayMode = MusicPlayMode.REGULAR, - testsoundtrack: dict[str, Any] | None = None) -> None: + def do_play_music( + self, + musictype: MusicType | str | None, + continuous: bool = False, + mode: MusicPlayMode = MusicPlayMode.REGULAR, + testsoundtrack: dict[str, Any] | None = None, + ) -> None: """Plays the requested music type/mode. For most cases, setmusic() is the proper call to use, which itself @@ -378,8 +393,9 @@ class MusicSubsystem: 'positional': False, 'music': True, 'volume': entry.volume * 5.0, - 'loop': entry.loop - }) + 'loop': entry.loop, + }, + ) class MusicPlayer: @@ -397,11 +413,16 @@ class MusicPlayer: self._volume = 1.0 self._actually_playing = False - def select_entry(self, callback: Callable[[Any], None], current_entry: Any, - selection_target_name: str) -> Any: + def select_entry( + self, + callback: Callable[[Any], None], + current_entry: Any, + selection_target_name: str, + ) -> Any: """Summons a UI to select a new soundtrack entry.""" - return self.on_select_entry(callback, current_entry, - selection_target_name) + return self.on_select_entry( + callback, current_entry, selection_target_name + ) def set_volume(self, volume: float) -> None: """Set player volume (value should be between 0 and 1).""" @@ -435,8 +456,12 @@ class MusicPlayer: """Shutdown music playback completely.""" self.on_app_shutdown() - def on_select_entry(self, callback: Callable[[Any], None], - current_entry: Any, selection_target_name: str) -> Any: + def on_select_entry( + self, + callback: Callable[[Any], None], + current_entry: Any, + selection_target_name: str, + ) -> Any: """Present a GUI to select an entry. The callback should be called with a valid entry or None to @@ -464,8 +489,9 @@ class MusicPlayer: self.on_play(self._entry_to_play) self._actually_playing = True else: - if self._actually_playing and (self._entry_to_play is None - or self._volume <= 0.0): + if self._actually_playing and ( + self._entry_to_play is None or self._volume <= 0.0 + ): self.on_stop() self._actually_playing = False diff --git a/dist/ba_data/python/ba/_net.py b/dist/ba_data/python/ba/_net.py index 7fac86f..2a0d421 100644 --- a/dist/ba_data/python/ba/_net.py +++ b/dist/ba_data/python/ba/_net.py @@ -15,6 +15,7 @@ import _ba if TYPE_CHECKING: from typing import Any, Callable import socket + MasterServerCallback = Callable[[None | dict[str, Any]], None] # Timeout for standard functions talking to the master-server/etc. @@ -61,6 +62,7 @@ class NetworkSubsystem: 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. @@ -84,16 +86,21 @@ def get_ip_address_type(addr: str) -> socket.AddressFamily: class MasterServerResponseType(Enum): """How to interpret responses from the master-server.""" + JSON = 0 class MasterServerCallThread(threading.Thread): """Thread to communicate with the master-server.""" - def __init__(self, request: str, request_type: str, - data: dict[str, Any] | None, - callback: MasterServerCallback | None, - response_type: MasterServerResponseType): + def __init__( + self, + request: str, + request_type: str, + data: dict[str, Any] | None, + callback: MasterServerCallback | None, + response_type: MasterServerResponseType, + ): super().__init__() self._request = request self._request_type = request_type @@ -106,8 +113,7 @@ class MasterServerCallThread(threading.Thread): # Save and restore the context we were created from. activity = _ba.getactivity(doraise=False) - self._activity = weakref.ref( - activity) if activity is not None else None + self._activity = weakref.ref(activity) if activity is not None else None def _run_callback(self, arg: None | dict[str, Any]) -> None: # If we were created in an activity context and that activity has @@ -143,22 +149,31 @@ class MasterServerCallThread(threading.Thread): self._data = utf8_all(self._data) _ba.set_thread_name('BA_ServerCallThread') if self._request_type == 'get': - url = (get_master_server_address() + '/' + self._request + - '?' + urllib.parse.urlencode(self._data)) + url = ( + get_master_server_address() + + '/' + + self._request + + '?' + + urllib.parse.urlencode(self._data) + ) response = urllib.request.urlopen( urllib.request.Request( - url, None, {'User-Agent': _ba.app.user_agent_string}), + url, None, {'User-Agent': _ba.app.user_agent_string} + ), context=_ba.app.net.sslcontext, - timeout=DEFAULT_REQUEST_TIMEOUT_SECONDS) + timeout=DEFAULT_REQUEST_TIMEOUT_SECONDS, + ) elif self._request_type == 'post': url = get_master_server_address() + '/' + self._request response = urllib.request.urlopen( urllib.request.Request( url, urllib.parse.urlencode(self._data).encode(), - {'User-Agent': _ba.app.user_agent_string}), + {'User-Agent': _ba.app.user_agent_string}, + ), context=_ba.app.net.sslcontext, - timeout=DEFAULT_REQUEST_TIMEOUT_SECONDS) + timeout=DEFAULT_REQUEST_TIMEOUT_SECONDS, + ) else: raise TypeError('Invalid request_type: ' + self._request_type) @@ -180,37 +195,43 @@ class MasterServerCallThread(threading.Thread): # Ignore common network errors; note unexpected ones. if not is_urllib_communication_error(exc, url=url): - print(f'Error in MasterServerCallThread' - f' (url={url},' - f' response-type={self._response_type},' - f' response-data={response_data}):') + print( + f'Error in MasterServerCallThread' + f' (url={url},' + f' response-type={self._response_type},' + f' response-data={response_data}):' + ) import traceback + traceback.print_exc() response_data = None if self._callback is not None: - _ba.pushcall(Call(self._run_callback, response_data), - from_other_thread=True) + _ba.pushcall( + Call(self._run_callback, response_data), from_other_thread=True + ) def master_server_get( request: str, data: dict[str, Any], callback: MasterServerCallback | None = None, - response_type: MasterServerResponseType = MasterServerResponseType.JSON + response_type: MasterServerResponseType = MasterServerResponseType.JSON, ) -> None: """Make a call to the master server via a http GET.""" - MasterServerCallThread(request, 'get', data, callback, - response_type).start() + MasterServerCallThread( + request, 'get', data, callback, response_type + ).start() def master_server_post( request: str, data: dict[str, Any], callback: MasterServerCallback | None = None, - response_type: MasterServerResponseType = MasterServerResponseType.JSON + response_type: MasterServerResponseType = MasterServerResponseType.JSON, ) -> None: """Make a call to the master server via a http POST.""" - MasterServerCallThread(request, 'post', data, callback, - response_type).start() + MasterServerCallThread( + request, 'post', data, callback, response_type + ).start() diff --git a/dist/ba_data/python/ba/_player.py b/dist/ba_data/python/ba/_player.py index 3e56fcf..32d1720 100644 --- a/dist/ba_data/python/ba/_player.py +++ b/dist/ba_data/python/ba/_player.py @@ -8,8 +8,11 @@ from dataclasses import dataclass from typing import TYPE_CHECKING, TypeVar, Generic, cast import _ba -from ba._error import (SessionPlayerNotFoundError, print_exception, - ActorNotFoundError) +from ba._error import ( + SessionPlayerNotFoundError, + print_exception, + ActorNotFoundError, +) from ba._messages import DeathType, DieMessage if TYPE_CHECKING: @@ -28,6 +31,7 @@ class PlayerInfo: Category: Gameplay Classes """ + name: str character: str @@ -38,6 +42,7 @@ class StandLocation: Category: Gameplay Classes """ + position: ba.Vec3 angle: float | None = None @@ -90,7 +95,8 @@ class Player(Generic[TeamType]): f' operator (__eq__) which will break internal' f' logic. Please remove it.\n' f'For dataclasses you can do "dataclass(eq=False)"' - f' in the class decorator.') + f' in the class decorator.' + ) self.actor = None self.character = '' @@ -249,8 +255,9 @@ class Player(Generic[TeamType]): assert not self._expired return self._sessionplayer.get_icon() - def assigninput(self, inputtype: ba.InputType | tuple[ba.InputType, ...], - call: Callable) -> None: + def assigninput( + self, inputtype: ba.InputType | tuple[ba.InputType, ...], call: Callable + ) -> None: """ Set the python callable to be run for one or more types of input. """ @@ -311,8 +318,9 @@ def playercast(totype: type[PlayerType], player: ba.Player) -> PlayerType: # NOTE: ideally we should have a single playercast() call and use overloads # for the optional variety, but that currently seems to not be working. # See: https://github.com/python/mypy/issues/8800 -def playercast_o(totype: type[PlayerType], - player: ba.Player | None) -> PlayerType | None: +def playercast_o( + totype: type[PlayerType], player: ba.Player | None +) -> PlayerType | None: """A variant of ba.playercast() for use with optional ba.Player values. Category: Gameplay Functions diff --git a/dist/ba_data/python/ba/_playlist.py b/dist/ba_data/python/ba/_playlist.py index 5518601..5576cfe 100644 --- a/dist/ba_data/python/ba/_playlist.py +++ b/dist/ba_data/python/ba/_playlist.py @@ -15,12 +15,14 @@ if TYPE_CHECKING: PlaylistType = list[dict[str, Any]] -def filter_playlist(playlist: PlaylistType, - sessiontype: type[_session.Session], - add_resolved_type: bool = False, - remove_unowned: bool = True, - mark_unowned: bool = False, - name: str = '?') -> PlaylistType: +def filter_playlist( + playlist: PlaylistType, + sessiontype: type[_session.Session], + add_resolved_type: bool = False, + remove_unowned: bool = True, + mark_unowned: bool = False, + name: str = '?', +) -> PlaylistType: """Return a filtered version of a playlist. Strips out or replaces invalid or unowned game types, makes sure all @@ -34,6 +36,7 @@ def filter_playlist(playlist: PlaylistType, from ba._store import get_unowned_maps, get_unowned_game_types from ba._general import getclass from ba._gameactivity import GameActivity + goodlist: list[dict] = [] unowned_maps: Sequence[str] if remove_unowned or mark_unowned: @@ -56,7 +59,8 @@ def filter_playlist(playlist: PlaylistType, # Update old map names to new ones. entry['settings']['map'] = get_filtered_map_name( - entry['settings']['map']) + entry['settings']['map'] + ) if remove_unowned and entry['settings']['map'] in unowned_maps: continue @@ -67,60 +71,89 @@ def filter_playlist(playlist: PlaylistType, raise TypeError('invalid entry format') try: # Do some type filters for backwards compat. - if entry['type'] in ('Assault.AssaultGame', - 'Happy_Thoughts.HappyThoughtsGame', - 'bsAssault.AssaultGame', - 'bs_assault.AssaultGame'): + if entry['type'] in ( + 'Assault.AssaultGame', + 'Happy_Thoughts.HappyThoughtsGame', + 'bsAssault.AssaultGame', + 'bs_assault.AssaultGame', + ): entry['type'] = 'bastd.game.assault.AssaultGame' - if entry['type'] in ('King_of_the_Hill.KingOfTheHillGame', - 'bsKingOfTheHill.KingOfTheHillGame', - 'bs_king_of_the_hill.KingOfTheHillGame'): + if entry['type'] in ( + 'King_of_the_Hill.KingOfTheHillGame', + 'bsKingOfTheHill.KingOfTheHillGame', + 'bs_king_of_the_hill.KingOfTheHillGame', + ): entry['type'] = 'bastd.game.kingofthehill.KingOfTheHillGame' - if entry['type'] in ('Capture_the_Flag.CTFGame', - 'bsCaptureTheFlag.CTFGame', - 'bs_capture_the_flag.CTFGame'): - entry['type'] = ( - 'bastd.game.capturetheflag.CaptureTheFlagGame') - if entry['type'] in ('Death_Match.DeathMatchGame', - 'bsDeathMatch.DeathMatchGame', - 'bs_death_match.DeathMatchGame'): + if entry['type'] in ( + 'Capture_the_Flag.CTFGame', + 'bsCaptureTheFlag.CTFGame', + 'bs_capture_the_flag.CTFGame', + ): + entry['type'] = 'bastd.game.capturetheflag.CaptureTheFlagGame' + if entry['type'] in ( + 'Death_Match.DeathMatchGame', + 'bsDeathMatch.DeathMatchGame', + 'bs_death_match.DeathMatchGame', + ): entry['type'] = 'bastd.game.deathmatch.DeathMatchGame' - if entry['type'] in ('ChosenOne.ChosenOneGame', - 'bsChosenOne.ChosenOneGame', - 'bs_chosen_one.ChosenOneGame'): + if entry['type'] in ( + 'ChosenOne.ChosenOneGame', + 'bsChosenOne.ChosenOneGame', + 'bs_chosen_one.ChosenOneGame', + ): entry['type'] = 'bastd.game.chosenone.ChosenOneGame' - if entry['type'] in ('Conquest.Conquest', 'Conquest.ConquestGame', - 'bsConquest.ConquestGame', - 'bs_conquest.ConquestGame'): + if entry['type'] in ( + 'Conquest.Conquest', + 'Conquest.ConquestGame', + 'bsConquest.ConquestGame', + 'bs_conquest.ConquestGame', + ): entry['type'] = 'bastd.game.conquest.ConquestGame' - if entry['type'] in ('Elimination.EliminationGame', - 'bsElimination.EliminationGame', - 'bs_elimination.EliminationGame'): + if entry['type'] in ( + 'Elimination.EliminationGame', + 'bsElimination.EliminationGame', + 'bs_elimination.EliminationGame', + ): entry['type'] = 'bastd.game.elimination.EliminationGame' - if entry['type'] in ('Football.FootballGame', - 'bsFootball.FootballTeamGame', - 'bs_football.FootballTeamGame'): + if entry['type'] in ( + 'Football.FootballGame', + 'bsFootball.FootballTeamGame', + 'bs_football.FootballTeamGame', + ): entry['type'] = 'bastd.game.football.FootballTeamGame' - if entry['type'] in ('Hockey.HockeyGame', 'bsHockey.HockeyGame', - 'bs_hockey.HockeyGame'): + if entry['type'] in ( + 'Hockey.HockeyGame', + 'bsHockey.HockeyGame', + 'bs_hockey.HockeyGame', + ): entry['type'] = 'bastd.game.hockey.HockeyGame' - if entry['type'] in ('Keep_Away.KeepAwayGame', - 'bsKeepAway.KeepAwayGame', - 'bs_keep_away.KeepAwayGame'): + if entry['type'] in ( + 'Keep_Away.KeepAwayGame', + 'bsKeepAway.KeepAwayGame', + 'bs_keep_away.KeepAwayGame', + ): entry['type'] = 'bastd.game.keepaway.KeepAwayGame' - if entry['type'] in ('Race.RaceGame', 'bsRace.RaceGame', - 'bs_race.RaceGame'): + if entry['type'] in ( + 'Race.RaceGame', + 'bsRace.RaceGame', + 'bs_race.RaceGame', + ): entry['type'] = 'bastd.game.race.RaceGame' - if entry['type'] in ('bsEasterEggHunt.EasterEggHuntGame', - 'bs_easter_egg_hunt.EasterEggHuntGame'): + if entry['type'] in ( + 'bsEasterEggHunt.EasterEggHuntGame', + 'bs_easter_egg_hunt.EasterEggHuntGame', + ): entry['type'] = 'bastd.game.easteregghunt.EasterEggHuntGame' - if entry['type'] in ('bsMeteorShower.MeteorShowerGame', - 'bs_meteor_shower.MeteorShowerGame'): + if entry['type'] in ( + 'bsMeteorShower.MeteorShowerGame', + 'bs_meteor_shower.MeteorShowerGame', + ): entry['type'] = 'bastd.game.meteorshower.MeteorShowerGame' - if entry['type'] in ('bsTargetPractice.TargetPracticeGame', - 'bs_target_practice.TargetPracticeGame'): - entry['type'] = ( - 'bastd.game.targetpractice.TargetPracticeGame') + if entry['type'] in ( + 'bsTargetPractice.TargetPracticeGame', + 'bs_target_practice.TargetPracticeGame', + ): + entry['type'] = 'bastd.game.targetpractice.TargetPracticeGame' gameclass = getclass(entry['type'], GameActivity) @@ -140,10 +173,12 @@ def filter_playlist(playlist: PlaylistType, entry['settings'][setting.name] = setting.default goodlist.append(entry) except ImportError as exc: - logging.warning('Import failed while scanning playlist \'%s\': %s', - name, exc) + logging.warning( + 'Import failed while scanning playlist \'%s\': %s', name, exc + ) except Exception: from ba import _error + _error.print_exception() return goodlist @@ -155,123 +190,134 @@ def get_default_free_for_all_playlist() -> PlaylistType: # but filtering translates them properly to the new ones. # (is kinda a handy way to ensure filtering is working). # Eventually should update these though. - return [{ - 'settings': { - 'Epic Mode': False, - 'Kills to Win Per Player': 10, - 'Respawn Times': 1.0, - 'Time Limit': 300, - 'map': 'Doom Shroom' + return [ + { + 'settings': { + 'Epic Mode': False, + 'Kills to Win Per Player': 10, + 'Respawn Times': 1.0, + 'Time Limit': 300, + 'map': 'Doom Shroom', + }, + 'type': 'bs_death_match.DeathMatchGame', }, - 'type': 'bs_death_match.DeathMatchGame' - }, { - 'settings': { - 'Chosen One Gets Gloves': True, - 'Chosen One Gets Shield': False, - 'Chosen One Time': 30, - 'Epic Mode': 0, - 'Respawn Times': 1.0, - 'Time Limit': 300, - 'map': 'Monkey Face' + { + 'settings': { + 'Chosen One Gets Gloves': True, + 'Chosen One Gets Shield': False, + 'Chosen One Time': 30, + 'Epic Mode': 0, + 'Respawn Times': 1.0, + 'Time Limit': 300, + 'map': 'Monkey Face', + }, + 'type': 'bs_chosen_one.ChosenOneGame', }, - 'type': 'bs_chosen_one.ChosenOneGame' - }, { - 'settings': { - 'Hold Time': 30, - 'Respawn Times': 1.0, - 'Time Limit': 300, - 'map': 'Zigzag' + { + 'settings': { + 'Hold Time': 30, + 'Respawn Times': 1.0, + 'Time Limit': 300, + 'map': 'Zigzag', + }, + 'type': 'bs_king_of_the_hill.KingOfTheHillGame', }, - 'type': 'bs_king_of_the_hill.KingOfTheHillGame' - }, { - 'settings': { - 'Epic Mode': False, - 'map': 'Rampage' + { + 'settings': {'Epic Mode': False, 'map': 'Rampage'}, + 'type': 'bs_meteor_shower.MeteorShowerGame', }, - 'type': 'bs_meteor_shower.MeteorShowerGame' - }, { - 'settings': { - 'Epic Mode': 1, - 'Lives Per Player': 1, - 'Respawn Times': 1.0, - 'Time Limit': 120, - 'map': 'Tip Top' + { + 'settings': { + 'Epic Mode': 1, + 'Lives Per Player': 1, + 'Respawn Times': 1.0, + 'Time Limit': 120, + 'map': 'Tip Top', + }, + 'type': 'bs_elimination.EliminationGame', }, - 'type': 'bs_elimination.EliminationGame' - }, { - 'settings': { - 'Hold Time': 30, - 'Respawn Times': 1.0, - 'Time Limit': 300, - 'map': 'The Pad' + { + 'settings': { + 'Hold Time': 30, + 'Respawn Times': 1.0, + 'Time Limit': 300, + 'map': 'The Pad', + }, + 'type': 'bs_keep_away.KeepAwayGame', }, - 'type': 'bs_keep_away.KeepAwayGame' - }, { - 'settings': { - 'Epic Mode': True, - 'Kills to Win Per Player': 10, - 'Respawn Times': 0.25, - 'Time Limit': 120, - 'map': 'Rampage' + { + 'settings': { + 'Epic Mode': True, + 'Kills to Win Per Player': 10, + 'Respawn Times': 0.25, + 'Time Limit': 120, + 'map': 'Rampage', + }, + 'type': 'bs_death_match.DeathMatchGame', }, - 'type': 'bs_death_match.DeathMatchGame' - }, { - 'settings': { - 'Bomb Spawning': 1000, - 'Epic Mode': False, - 'Laps': 3, - 'Mine Spawn Interval': 4000, - 'Mine Spawning': 4000, - 'Time Limit': 300, - 'map': 'Big G' + { + 'settings': { + 'Bomb Spawning': 1000, + 'Epic Mode': False, + 'Laps': 3, + 'Mine Spawn Interval': 4000, + 'Mine Spawning': 4000, + 'Time Limit': 300, + 'map': 'Big G', + }, + 'type': 'bs_race.RaceGame', }, - 'type': 'bs_race.RaceGame' - }, { - 'settings': { - 'Hold Time': 30, - 'Respawn Times': 1.0, - 'Time Limit': 300, - 'map': 'Happy Thoughts' + { + 'settings': { + 'Hold Time': 30, + 'Respawn Times': 1.0, + 'Time Limit': 300, + 'map': 'Happy Thoughts', + }, + 'type': 'bs_king_of_the_hill.KingOfTheHillGame', }, - 'type': 'bs_king_of_the_hill.KingOfTheHillGame' - }, { - 'settings': { - 'Enable Impact Bombs': 1, - 'Enable Triple Bombs': False, - 'Target Count': 2, - 'map': 'Doom Shroom' + { + 'settings': { + 'Enable Impact Bombs': 1, + 'Enable Triple Bombs': False, + 'Target Count': 2, + 'map': 'Doom Shroom', + }, + 'type': 'bs_target_practice.TargetPracticeGame', }, - 'type': 'bs_target_practice.TargetPracticeGame' - }, { - 'settings': { - 'Epic Mode': False, - 'Lives Per Player': 5, - 'Respawn Times': 1.0, - 'Time Limit': 300, - 'map': 'Step Right Up' + { + 'settings': { + 'Epic Mode': False, + 'Lives Per Player': 5, + 'Respawn Times': 1.0, + 'Time Limit': 300, + 'map': 'Step Right Up', + }, + 'type': 'bs_elimination.EliminationGame', }, - 'type': 'bs_elimination.EliminationGame' - }, { - 'settings': { - 'Epic Mode': False, - 'Kills to Win Per Player': 10, - 'Respawn Times': 1.0, - 'Time Limit': 300, - 'map': 'Crag Castle' + { + 'settings': { + 'Epic Mode': False, + 'Kills to Win Per Player': 10, + 'Respawn Times': 1.0, + 'Time Limit': 300, + 'map': 'Crag Castle', + }, + 'type': 'bs_death_match.DeathMatchGame', }, - 'type': 'bs_death_match.DeathMatchGame' - }, { - 'map': 'Lake Frigid', - 'settings': { - 'Bomb Spawning': 0, - 'Epic Mode': False, - 'Laps': 6, - 'Mine Spawning': 2000, - 'Time Limit': 300, - 'map': 'Lake Frigid' + { + 'map': 'Lake Frigid', + 'settings': { + 'Bomb Spawning': 0, + 'Epic Mode': False, + 'Laps': 6, + 'Mine Spawning': 2000, + 'Time Limit': 300, + 'map': 'Lake Frigid', + }, + 'type': 'bs_race.RaceGame', }, - 'type': 'bs_race.RaceGame' - }] + ] def get_default_teams_playlist() -> PlaylistType: @@ -281,217 +327,238 @@ def get_default_teams_playlist() -> PlaylistType: # but filtering translates them properly to the new ones. # (is kinda a handy way to ensure filtering is working). # Eventually should update these though. - return [{ - 'settings': { - 'Epic Mode': False, - 'Flag Idle Return Time': 30, - 'Flag Touch Return Time': 0, - 'Respawn Times': 1.0, - 'Score to Win': 3, - 'Time Limit': 600, - 'map': 'Bridgit' + return [ + { + 'settings': { + 'Epic Mode': False, + 'Flag Idle Return Time': 30, + 'Flag Touch Return Time': 0, + 'Respawn Times': 1.0, + 'Score to Win': 3, + 'Time Limit': 600, + 'map': 'Bridgit', + }, + 'type': 'bs_capture_the_flag.CTFGame', }, - 'type': 'bs_capture_the_flag.CTFGame' - }, { - 'settings': { - 'Epic Mode': False, - 'Respawn Times': 1.0, - 'Score to Win': 3, - 'Time Limit': 600, - 'map': 'Step Right Up' + { + 'settings': { + 'Epic Mode': False, + 'Respawn Times': 1.0, + 'Score to Win': 3, + 'Time Limit': 600, + 'map': 'Step Right Up', + }, + 'type': 'bs_assault.AssaultGame', }, - 'type': 'bs_assault.AssaultGame' - }, { - 'settings': { - 'Balance Total Lives': False, - 'Epic Mode': False, - 'Lives Per Player': 3, - 'Respawn Times': 1.0, - 'Solo Mode': True, - 'Time Limit': 600, - 'map': 'Rampage' + { + 'settings': { + 'Balance Total Lives': False, + 'Epic Mode': False, + 'Lives Per Player': 3, + 'Respawn Times': 1.0, + 'Solo Mode': True, + 'Time Limit': 600, + 'map': 'Rampage', + }, + 'type': 'bs_elimination.EliminationGame', }, - 'type': 'bs_elimination.EliminationGame' - }, { - 'settings': { - 'Epic Mode': False, - 'Kills to Win Per Player': 5, - 'Respawn Times': 1.0, - 'Time Limit': 300, - 'map': 'Roundabout' + { + 'settings': { + 'Epic Mode': False, + 'Kills to Win Per Player': 5, + 'Respawn Times': 1.0, + 'Time Limit': 300, + 'map': 'Roundabout', + }, + 'type': 'bs_death_match.DeathMatchGame', }, - 'type': 'bs_death_match.DeathMatchGame' - }, { - 'settings': { - 'Respawn Times': 1.0, - 'Score to Win': 1, - 'Time Limit': 600, - 'map': 'Hockey Stadium' + { + 'settings': { + 'Respawn Times': 1.0, + 'Score to Win': 1, + 'Time Limit': 600, + 'map': 'Hockey Stadium', + }, + 'type': 'bs_hockey.HockeyGame', }, - 'type': 'bs_hockey.HockeyGame' - }, { - 'settings': { - 'Hold Time': 30, - 'Respawn Times': 1.0, - 'Time Limit': 300, - 'map': 'Monkey Face' + { + 'settings': { + 'Hold Time': 30, + 'Respawn Times': 1.0, + 'Time Limit': 300, + 'map': 'Monkey Face', + }, + 'type': 'bs_keep_away.KeepAwayGame', }, - 'type': 'bs_keep_away.KeepAwayGame' - }, { - 'settings': { - 'Balance Total Lives': False, - 'Epic Mode': True, - 'Lives Per Player': 1, - 'Respawn Times': 1.0, - 'Solo Mode': False, - 'Time Limit': 120, - 'map': 'Tip Top' + { + 'settings': { + 'Balance Total Lives': False, + 'Epic Mode': True, + 'Lives Per Player': 1, + 'Respawn Times': 1.0, + 'Solo Mode': False, + 'Time Limit': 120, + 'map': 'Tip Top', + }, + 'type': 'bs_elimination.EliminationGame', }, - 'type': 'bs_elimination.EliminationGame' - }, { - 'settings': { - 'Epic Mode': False, - 'Respawn Times': 1.0, - 'Score to Win': 3, - 'Time Limit': 300, - 'map': 'Crag Castle' + { + 'settings': { + 'Epic Mode': False, + 'Respawn Times': 1.0, + 'Score to Win': 3, + 'Time Limit': 300, + 'map': 'Crag Castle', + }, + 'type': 'bs_assault.AssaultGame', }, - 'type': 'bs_assault.AssaultGame' - }, { - 'settings': { - 'Epic Mode': False, - 'Kills to Win Per Player': 5, - 'Respawn Times': 1.0, - 'Time Limit': 300, - 'map': 'Doom Shroom' + { + 'settings': { + 'Epic Mode': False, + 'Kills to Win Per Player': 5, + 'Respawn Times': 1.0, + 'Time Limit': 300, + 'map': 'Doom Shroom', + }, + 'type': 'bs_death_match.DeathMatchGame', }, - 'type': 'bs_death_match.DeathMatchGame' - }, { - 'settings': { - 'Epic Mode': False, - 'map': 'Rampage' + { + 'settings': {'Epic Mode': False, 'map': 'Rampage'}, + 'type': 'bs_meteor_shower.MeteorShowerGame', }, - 'type': 'bs_meteor_shower.MeteorShowerGame' - }, { - 'settings': { - 'Epic Mode': False, - 'Flag Idle Return Time': 30, - 'Flag Touch Return Time': 0, - 'Respawn Times': 1.0, - 'Score to Win': 2, - 'Time Limit': 600, - 'map': 'Roundabout' + { + 'settings': { + 'Epic Mode': False, + 'Flag Idle Return Time': 30, + 'Flag Touch Return Time': 0, + 'Respawn Times': 1.0, + 'Score to Win': 2, + 'Time Limit': 600, + 'map': 'Roundabout', + }, + 'type': 'bs_capture_the_flag.CTFGame', }, - 'type': 'bs_capture_the_flag.CTFGame' - }, { - 'settings': { - 'Respawn Times': 1.0, - 'Score to Win': 21, - 'Time Limit': 600, - 'map': 'Football Stadium' + { + 'settings': { + 'Respawn Times': 1.0, + 'Score to Win': 21, + 'Time Limit': 600, + 'map': 'Football Stadium', + }, + 'type': 'bs_football.FootballTeamGame', }, - 'type': 'bs_football.FootballTeamGame' - }, { - 'settings': { - 'Epic Mode': True, - 'Respawn Times': 0.25, - 'Score to Win': 3, - 'Time Limit': 120, - 'map': 'Bridgit' + { + 'settings': { + 'Epic Mode': True, + 'Respawn Times': 0.25, + 'Score to Win': 3, + 'Time Limit': 120, + 'map': 'Bridgit', + }, + 'type': 'bs_assault.AssaultGame', }, - 'type': 'bs_assault.AssaultGame' - }, { - 'map': 'Doom Shroom', - 'settings': { - 'Enable Impact Bombs': 1, - 'Enable Triple Bombs': False, - 'Target Count': 2, - 'map': 'Doom Shroom' + { + 'map': 'Doom Shroom', + 'settings': { + 'Enable Impact Bombs': 1, + 'Enable Triple Bombs': False, + 'Target Count': 2, + 'map': 'Doom Shroom', + }, + 'type': 'bs_target_practice.TargetPracticeGame', }, - 'type': 'bs_target_practice.TargetPracticeGame' - }, { - 'settings': { - 'Hold Time': 30, - 'Respawn Times': 1.0, - 'Time Limit': 300, - 'map': 'Tip Top' + { + 'settings': { + 'Hold Time': 30, + 'Respawn Times': 1.0, + 'Time Limit': 300, + 'map': 'Tip Top', + }, + 'type': 'bs_king_of_the_hill.KingOfTheHillGame', }, - 'type': 'bs_king_of_the_hill.KingOfTheHillGame' - }, { - 'settings': { - 'Epic Mode': False, - 'Respawn Times': 1.0, - 'Score to Win': 2, - 'Time Limit': 300, - 'map': 'Zigzag' + { + 'settings': { + 'Epic Mode': False, + 'Respawn Times': 1.0, + 'Score to Win': 2, + 'Time Limit': 300, + 'map': 'Zigzag', + }, + 'type': 'bs_assault.AssaultGame', }, - 'type': 'bs_assault.AssaultGame' - }, { - 'settings': { - 'Epic Mode': False, - 'Flag Idle Return Time': 30, - 'Flag Touch Return Time': 0, - 'Respawn Times': 1.0, - 'Score to Win': 3, - 'Time Limit': 300, - 'map': 'Happy Thoughts' + { + 'settings': { + 'Epic Mode': False, + 'Flag Idle Return Time': 30, + 'Flag Touch Return Time': 0, + 'Respawn Times': 1.0, + 'Score to Win': 3, + 'Time Limit': 300, + 'map': 'Happy Thoughts', + }, + 'type': 'bs_capture_the_flag.CTFGame', }, - 'type': 'bs_capture_the_flag.CTFGame' - }, { - 'settings': { - 'Bomb Spawning': 1000, - 'Epic Mode': True, - 'Laps': 1, - 'Mine Spawning': 2000, - 'Time Limit': 300, - 'map': 'Big G' + { + 'settings': { + 'Bomb Spawning': 1000, + 'Epic Mode': True, + 'Laps': 1, + 'Mine Spawning': 2000, + 'Time Limit': 300, + 'map': 'Big G', + }, + 'type': 'bs_race.RaceGame', }, - 'type': 'bs_race.RaceGame' - }, { - 'settings': { - 'Epic Mode': False, - 'Kills to Win Per Player': 5, - 'Respawn Times': 1.0, - 'Time Limit': 300, - 'map': 'Monkey Face' + { + 'settings': { + 'Epic Mode': False, + 'Kills to Win Per Player': 5, + 'Respawn Times': 1.0, + 'Time Limit': 300, + 'map': 'Monkey Face', + }, + 'type': 'bs_death_match.DeathMatchGame', }, - 'type': 'bs_death_match.DeathMatchGame' - }, { - 'settings': { - 'Hold Time': 30, - 'Respawn Times': 1.0, - 'Time Limit': 300, - 'map': 'Lake Frigid' + { + 'settings': { + 'Hold Time': 30, + 'Respawn Times': 1.0, + 'Time Limit': 300, + 'map': 'Lake Frigid', + }, + 'type': 'bs_keep_away.KeepAwayGame', }, - 'type': 'bs_keep_away.KeepAwayGame' - }, { - 'settings': { - 'Epic Mode': False, - 'Flag Idle Return Time': 30, - 'Flag Touch Return Time': 3, - 'Respawn Times': 1.0, - 'Score to Win': 2, - 'Time Limit': 300, - 'map': 'Tip Top' + { + 'settings': { + 'Epic Mode': False, + 'Flag Idle Return Time': 30, + 'Flag Touch Return Time': 3, + 'Respawn Times': 1.0, + 'Score to Win': 2, + 'Time Limit': 300, + 'map': 'Tip Top', + }, + 'type': 'bs_capture_the_flag.CTFGame', }, - 'type': 'bs_capture_the_flag.CTFGame' - }, { - 'settings': { - 'Balance Total Lives': False, - 'Epic Mode': False, - 'Lives Per Player': 3, - 'Respawn Times': 1.0, - 'Solo Mode': False, - 'Time Limit': 300, - 'map': 'Crag Castle' + { + 'settings': { + 'Balance Total Lives': False, + 'Epic Mode': False, + 'Lives Per Player': 3, + 'Respawn Times': 1.0, + 'Solo Mode': False, + 'Time Limit': 300, + 'map': 'Crag Castle', + }, + 'type': 'bs_elimination.EliminationGame', }, - 'type': 'bs_elimination.EliminationGame' - }, { - 'settings': { - 'Epic Mode': True, - 'Respawn Times': 0.25, - 'Time Limit': 120, - 'map': 'Zigzag' + { + 'settings': { + 'Epic Mode': True, + 'Respawn Times': 0.25, + 'Time Limit': 120, + 'map': 'Zigzag', + }, + 'type': 'bs_conquest.ConquestGame', }, - 'type': 'bs_conquest.ConquestGame' - }] + ] diff --git a/dist/ba_data/python/ba/_plugin.py b/dist/ba_data/python/ba/_plugin.py index d05cdbe..3e89688 100644 --- a/dist/ba_data/python/ba/_plugin.py +++ b/dist/ba_data/python/ba/_plugin.py @@ -42,9 +42,12 @@ class PluginSubsystem: # Create a potential-plugin for each class we found in the scan. for class_path in results.exports_of_class(Plugin): plugs.potential_plugins.append( - PotentialPlugin(display_name=Lstr(value=class_path), - class_path=class_path, - available=True)) + PotentialPlugin( + display_name=Lstr(value=class_path), + class_path=class_path, + available=True, + ) + ) if class_path not in plugstates: # Go ahead and enable new plugins by default, but we'll # inform the user that they need to restart to pick them up. @@ -59,8 +62,9 @@ class PluginSubsystem: # plugins, so we don't need the message about 'restart to activate' # anymore. if found_new and bool(False): - _ba.screenmessage(Lstr(resource='pluginsDetectedText'), - color=(0, 1, 0)) + _ba.screenmessage( + Lstr(resource='pluginsDetectedText'), color=(0, 1, 0) + ) _ba.playsound(_ba.getsound('ding')) if config_changed: @@ -75,6 +79,7 @@ class PluginSubsystem: plugin.on_app_running() except Exception: from ba import _error + _error.print_exception('Error in plugin on_app_running()') def on_app_pause(self) -> None: @@ -84,6 +89,7 @@ class PluginSubsystem: plugin.on_app_pause() except Exception: from ba import _error + _error.print_exception('Error in plugin on_app_pause()') def on_app_resume(self) -> None: @@ -93,6 +99,7 @@ class PluginSubsystem: plugin.on_app_resume() except Exception: from ba import _error + _error.print_exception('Error in plugin on_app_resume()') def on_app_shutdown(self) -> None: @@ -102,6 +109,7 @@ class PluginSubsystem: plugin.on_app_shutdown() except Exception: from ba import _error + _error.print_exception('Error in plugin on_app_shutdown()') def load_plugins(self) -> None: @@ -113,8 +121,9 @@ class PluginSubsystem: # in the app config. Its not our job to look at meta stuff here. plugstates: dict[str, dict] = _ba.app.config.get('Plugins', {}) assert isinstance(plugstates, dict) - plugkeys: list[str] = sorted(key for key, val in plugstates.items() - if val.get('enabled', False)) + plugkeys: list[str] = sorted( + key for key, val in plugstates.items() if val.get('enabled', False) + ) disappeared_plugs: set[str] = set() for plugkey in plugkeys: try: @@ -124,10 +133,13 @@ class PluginSubsystem: continue except Exception as exc: _ba.playsound(_ba.getsound('error')) - _ba.screenmessage(Lstr(resource='pluginClassLoadErrorText', - subs=[('${PLUGIN}', plugkey), - ('${ERROR}', str(exc))]), - color=(1, 0, 0)) + _ba.screenmessage( + Lstr( + resource='pluginClassLoadErrorText', + subs=[('${PLUGIN}', plugkey), ('${ERROR}', str(exc))], + ), + color=(1, 0, 0), + ) logging.exception("Error loading plugin class '%s'", plugkey) continue try: @@ -136,11 +148,15 @@ class PluginSubsystem: self.active_plugins[plugkey] = plugin except Exception as exc: from ba import _error + _ba.playsound(_ba.getsound('error')) - _ba.screenmessage(Lstr(resource='pluginInitErrorText', - subs=[('${PLUGIN}', plugkey), - ('${ERROR}', str(exc))]), - color=(1, 0, 0)) + _ba.screenmessage( + Lstr( + resource='pluginInitErrorText', + subs=[('${PLUGIN}', plugkey), ('${ERROR}', str(exc))], + ), + color=(1, 0, 0), + ) _error.print_exception(f"Error initing plugin: '{plugkey}'.") # If plugins disappeared, let the user know gently and remove them @@ -150,13 +166,18 @@ class PluginSubsystem: if disappeared_plugs: _ba.playsound(_ba.getsound('shieldDown')) _ba.screenmessage( - Lstr(resource='pluginsRemovedText', - subs=[('${NUM}', str(len(disappeared_plugs)))]), + Lstr( + resource='pluginsRemovedText', + subs=[('${NUM}', str(len(disappeared_plugs)))], + ), color=(1, 1, 0), ) plugnames = ', '.join(disappeared_plugs) - logging.warning('%d plugin(s) no longer found: %s.', - len(disappeared_plugs), plugnames) + logging.warning( + '%d plugin(s) no longer found: %s.', + len(disappeared_plugs), + plugnames, + ) for goneplug in disappeared_plugs: del _ba.app.config['Plugins'][goneplug] _ba.app.config.commit() @@ -173,6 +194,7 @@ class PotentialPlugin: were previously set to be loaded but which were unable to be for some reason. In that case, 'available' will be set to False. """ + display_name: ba.Lstr class_path: str available: bool @@ -200,3 +222,10 @@ class Plugin: def on_app_shutdown(self) -> None: """Called before closing the application.""" + + def has_settings_ui(self) -> bool: + """Called to ask if we have settings UI we can show.""" + return False + + def show_settings_ui(self, source_widget: ba.Widget | None) -> None: + """Called to show our settings UI.""" diff --git a/dist/ba_data/python/ba/_powerup.py b/dist/ba_data/python/ba/_powerup.py index 8ca65a0..53d662a 100644 --- a/dist/ba_data/python/ba/_powerup.py +++ b/dist/ba_data/python/ba/_powerup.py @@ -45,6 +45,14 @@ class PowerupAcceptMessage: def get_default_powerup_distribution() -> Sequence[tuple[str, int]]: """Standard set of powerups.""" - return (('triple_bombs', 3), ('ice_bombs', 3), ('punch', 3), - ('impact_bombs', 3), ('land_mines', 2), ('sticky_bombs', 3), - ('shield', 2), ('health', 1), ('curse', 1)) + return ( + ('triple_bombs', 3), + ('ice_bombs', 3), + ('punch', 3), + ('impact_bombs', 3), + ('land_mines', 2), + ('sticky_bombs', 3), + ('shield', 2), + ('health', 1), + ('curse', 1), + ) diff --git a/dist/ba_data/python/ba/_profile.py b/dist/ba_data/python/ba/_profile.py index 308b6bf..2a24d98 100644 --- a/dist/ba_data/python/ba/_profile.py +++ b/dist/ba_data/python/ba/_profile.py @@ -13,11 +13,24 @@ if TYPE_CHECKING: # NOTE: player color options are enforced server-side for non-pro accounts # so don't change these or they won't stick... -PLAYER_COLORS = [(1, 0.15, 0.15), (0.2, 1, 0.2), (0.1, 0.1, 1), (0.2, 1, 1), - (0.5, 0.25, 1.0), (1, 1, 0), (1, 0.5, 0), (1, 0.3, 0.5), - (0.1, 0.1, 0.5), (0.4, 0.2, 0.1), (0.1, 0.35, 0.1), - (1, 0.8, 0.5), (0.4, 0.05, 0.05), (0.13, 0.13, 0.13), - (0.5, 0.5, 0.5), (1, 1, 1)] +PLAYER_COLORS = [ + (1, 0.15, 0.15), + (0.2, 1, 0.2), + (0.1, 0.1, 1), + (0.2, 1, 1), + (0.5, 0.25, 1.0), + (1, 1, 0), + (1, 0.5, 0), + (1, 0.3, 0.5), + (0.1, 0.1, 0.5), + (0.4, 0.2, 0.1), + (0.1, 0.35, 0.1), + (1, 0.8, 0.5), + (0.4, 0.05, 0.05), + (0.13, 0.13, 0.13), + (0.5, 0.5, 0.5), + (1, 1, 1), +] def get_player_colors() -> list[tuple[float, float, float]]: @@ -49,8 +62,7 @@ def get_player_profile_icon(profilename: str) -> str: def get_player_profile_colors( - profilename: str | None, - profiles: dict[str, dict[str, Any]] | None = None + profilename: str | None, profiles: dict[str, dict[str, Any]] | None = None ) -> tuple[tuple[float, float, float], tuple[float, float, float]]: """Given a profile, return colors for them.""" appconfig = _ba.app.config @@ -83,11 +95,13 @@ def get_player_profile_colors( if profilename is None: # Last 2 are grey and white; ignore those or we # get lots of old-looking players. - highlight = PLAYER_COLORS[random.randrange( - len(PLAYER_COLORS) - 2)] + highlight = PLAYER_COLORS[ + random.randrange(len(PLAYER_COLORS) - 2) + ] else: - highlight = PLAYER_COLORS[sum(ord(c) + 1 - for c in profilename) % - (len(PLAYER_COLORS) - 2)] + highlight = PLAYER_COLORS[ + sum(ord(c) + 1 for c in profilename) + % (len(PLAYER_COLORS) - 2) + ] return color, highlight diff --git a/dist/ba_data/python/ba/_score.py b/dist/ba_data/python/ba/_score.py index 8b8d009..b371e69 100644 --- a/dist/ba_data/python/ba/_score.py +++ b/dist/ba_data/python/ba/_score.py @@ -18,6 +18,7 @@ class ScoreType(Enum): Category: **Enums** """ + SECONDS = 's' MILLISECONDS = 'ms' POINTS = 'p' diff --git a/dist/ba_data/python/ba/_servermode.py b/dist/ba_data/python/ba/_servermode.py index 8c8ed98..f5a4055 100644 --- a/dist/ba_data/python/ba/_servermode.py +++ b/dist/ba_data/python/ba/_servermode.py @@ -9,13 +9,18 @@ import logging from typing import TYPE_CHECKING from efro.terminal import Clr -from bacommon.servermanager import (ServerCommand, StartServerModeCommand, - ShutdownCommand, ShutdownReason, - ChatMessageCommand, ScreenMessageCommand, - ClientListCommand, KickCommand) +from bacommon.servermanager import ( + ServerCommand, + StartServerModeCommand, + ShutdownCommand, + ShutdownReason, + ChatMessageCommand, + ScreenMessageCommand, + ClientListCommand, + KickCommand, +) import _ba -from ba._internal import (add_transaction, run_transactions, - get_v1_account_state) +from ba._internal import add_transaction, run_transactions, get_v1_account_state from ba._generated.enums import TimeType from ba._freeforallsession import FreeForAllSession from ba._dualteamsession import DualTeamSession @@ -31,6 +36,7 @@ if TYPE_CHECKING: def _cmd(command_data: bytes) -> None: """Handle commands coming in from our server manager parent process.""" import pickle + command = pickle.loads(command_data) assert isinstance(command, ServerCommand) @@ -41,8 +47,9 @@ def _cmd(command_data: bytes) -> None: if isinstance(command, ShutdownCommand): assert _ba.app.server is not None - _ba.app.server.shutdown(reason=command.reason, - immediate=command.immediate) + _ba.app.server.shutdown( + reason=command.reason, immediate=command.immediate + ) return if isinstance(command, ChatMessageCommand): @@ -56,10 +63,12 @@ def _cmd(command_data: bytes) -> None: # Note: we have to do transient messages if # clients is specified, so they won't show up # in replays. - _ba.screenmessage(command.message, - color=command.color, - clients=command.clients, - transient=command.clients is not None) + _ba.screenmessage( + command.message, + color=command.color, + clients=command.clients, + transient=command.clients is not None, + ) return if isinstance(command, ClientListCommand): @@ -69,12 +78,15 @@ def _cmd(command_data: bytes) -> None: if isinstance(command, KickCommand): assert _ba.app.server is not None - _ba.app.server.kick(client_id=command.client_id, - ban_time=command.ban_time) + _ba.app.server.kick( + client_id=command.client_id, ban_time=command.ban_time + ) return - print(f'{Clr.SRED}ERROR: server process' - f' got unknown command: {type(command)}{Clr.RST}') + print( + f'{Clr.SRED}ERROR: server process' + f' got unknown command: {type(command)}{Clr.RST}' + ) class ServerController: @@ -105,23 +117,28 @@ class ServerController: # account sign-in or fetching playlists; this will kick off the # session once done. with _ba.Context('ui'): - self._prep_timer = _ba.Timer(0.25, - self._prepare_to_serve, - timetype=TimeType.REAL, - repeat=True) + self._prep_timer = _ba.Timer( + 0.25, + self._prepare_to_serve, + timetype=TimeType.REAL, + repeat=True, + ) def print_client_list(self) -> None: """Print info about all connected clients.""" import json + roster = _ba.get_game_roster() title1 = 'Client ID' title2 = 'Account Name' title3 = 'Players' col1 = 10 col2 = 16 - out = (f'{Clr.BLD}' - f'{title1:<{col1}} {title2:<{col2}} {title3}' - f'{Clr.RST}') + out = ( + f'{Clr.BLD}' + f'{title1:<{col1}} {title2:<{col2}} {title3}' + f'{Clr.RST}' + ) for client in roster: if client['client_id'] == -1: continue @@ -153,9 +170,11 @@ class ServerController: print(f'{Clr.SBLU}Immediate shutdown initiated.{Clr.RST}') self._execute_shutdown() else: - print(f'{Clr.SBLU}Shutdown initiated;' - f' server process will exit at the next clean opportunity.' - f'{Clr.RST}') + print( + f'{Clr.SBLU}Shutdown initiated;' + f' server process will exit at the next clean opportunity.' + f'{Clr.RST}' + ) def handle_transition(self) -> bool: """Handle transitioning to a new ba.Session or quitting the app. @@ -172,37 +191,45 @@ class ServerController: def _execute_shutdown(self) -> None: from ba._language import Lstr + if self._executing_shutdown: return self._executing_shutdown = True timestrval = time.strftime('%c') if self._shutdown_reason is ShutdownReason.RESTARTING: - _ba.screenmessage(Lstr(resource='internal.serverRestartingText'), - color=(1, 0.5, 0.0)) - print(f'{Clr.SBLU}Exiting for server-restart' - f' at {timestrval}.{Clr.RST}') + _ba.screenmessage( + Lstr(resource='internal.serverRestartingText'), + color=(1, 0.5, 0.0), + ) + print( + f'{Clr.SBLU}Exiting for server-restart' + f' at {timestrval}.{Clr.RST}' + ) else: - _ba.screenmessage(Lstr(resource='internal.serverShuttingDownText'), - color=(1, 0.5, 0.0)) - print(f'{Clr.SBLU}Exiting for server-shutdown' - f' at {timestrval}.{Clr.RST}') + _ba.screenmessage( + Lstr(resource='internal.serverShuttingDownText'), + color=(1, 0.5, 0.0), + ) + print( + f'{Clr.SBLU}Exiting for server-shutdown' + f' at {timestrval}.{Clr.RST}' + ) with _ba.Context('ui'): _ba.timer(2.0, _ba.quit, timetype=TimeType.REAL) def _run_access_check(self) -> None: """Check with the master server to see if we're likely joinable.""" from ba._net import master_server_get + master_server_get( 'bsAccessCheck', - { - 'port': _ba.get_game_port(), - 'b': _ba.app.build_number - }, + {'port': _ba.get_game_port(), 'b': _ba.app.build_number}, callback=self._access_check_response, ) def _access_check_response(self, data: dict[str, Any] | None) -> None: import os + if data is None: print('error on UDP port access check (internet down?)') else: @@ -216,17 +243,22 @@ class ServerController: addrstr = '' poststr = ( '\nSet environment variable BA_ACCESS_CHECK_VERBOSE=1' - ' for more info.') + ' for more info.' + ) if data['accessible']: - print(f'{Clr.SBLU}Master server access check of{addrstr}' - f' udp port {port} succeeded.\n' - f'Your server appears to be' - f' joinable from the internet.{poststr}{Clr.RST}') + print( + f'{Clr.SBLU}Master server access check of{addrstr}' + f' udp port {port} succeeded.\n' + f'Your server appears to be' + f' joinable from the internet.{poststr}{Clr.RST}' + ) else: - print(f'{Clr.SRED}Master server access check of{addrstr}' - f' udp port {port} failed.\n' - f'Your server does not appear to be' - f' joinable from the internet.{poststr}{Clr.RST}') + print( + f'{Clr.SRED}Master server access check of{addrstr}' + f' udp port {port} failed.\n' + f'Your server does not appear to be' + f' joinable from the internet.{poststr}{Clr.RST}' + ) def _prepare_to_serve(self) -> None: """Run in a timer to do prep before beginning to serve.""" @@ -248,15 +280,18 @@ class ServerController: can_launch = True else: if not self._playlist_fetch_sent_request: - print(f'{Clr.SBLU}Requesting shared-playlist' - f' {self._config.playlist_code}...{Clr.RST}') + print( + f'{Clr.SBLU}Requesting shared-playlist' + f' {self._config.playlist_code}...{Clr.RST}' + ) add_transaction( { 'type': 'IMPORT_PLAYLIST', 'code': str(self._config.playlist_code), - 'overwrite': True + 'overwrite': True, }, - callback=self._on_playlist_fetch_response) + callback=self._on_playlist_fetch_response, + ) run_transactions() self._playlist_fetch_sent_request = True @@ -278,13 +313,17 @@ class ServerController: # Once we get here, simply modify our config to use this playlist. typename = ( - 'teams' if result['playlistType'] == 'Team Tournament' else - 'ffa' if result['playlistType'] == 'Free-for-All' else '??') + 'teams' + if result['playlistType'] == 'Team Tournament' + else 'ffa' + if result['playlistType'] == 'Free-for-All' + else '??' + ) plistname = result['playlistName'] print(f'{Clr.SBLU}Got playlist: "{plistname}" ({typename}).{Clr.RST}') self._playlist_fetch_got_response = True self._config.session_type = typename - self._playlist_name = (result['playlistName']) + self._playlist_name = result['playlistName'] def _get_session_type(self) -> type[ba.Session]: # Convert string session type to the class. @@ -296,7 +335,8 @@ class ServerController: if self._config.session_type == 'coop': return CoopSession raise RuntimeError( - f'Invalid session_type: "{self._config.session_type}"') + f'Invalid session_type: "{self._config.session_type}"' + ) def _launch_server_session(self) -> None: """Kick off a host-session based on the current server config.""" @@ -306,13 +346,17 @@ class ServerController: sessiontype = self._get_session_type() if get_v1_account_state() != 'signed_in': - print('WARNING: launch_server_session() expects to run ' - 'with a signed in server account') + print( + 'WARNING: launch_server_session() expects to run ' + 'with a signed in server account' + ) # If we didn't fetch a playlist but there's an inline one in the # server-config, pull it in to the game config and use it. - if (self._config.playlist_code is None - and self._config.playlist_inline is not None): + if ( + self._config.playlist_code is None + and self._config.playlist_inline is not None + ): self._playlist_name = 'ServerModePlaylist' if sessiontype is FreeForAllSession: ptypename = 'Free-for-All' @@ -325,12 +369,14 @@ class ServerController: # Need to add this in a transaction instead of just setting # it directly or it will get overwritten by the master-server. - add_transaction({ - 'type': 'ADD_PLAYLIST', - 'playlistType': ptypename, - 'playlistName': self._playlist_name, - 'playlist': self._config.playlist_inline - }) + add_transaction( + { + 'type': 'ADD_PLAYLIST', + 'playlistType': ptypename, + 'playlistName': self._playlist_name, + 'playlist': self._config.playlist_inline, + } + ) run_transactions() if self._first_run: @@ -338,17 +384,20 @@ class ServerController: startupmsg = ( f'{Clr.BLD}{Clr.BLU}{_ba.appnameupper()} {app.version}' f' ({app.build_number})' - f' entering server-mode {curtimestr}{Clr.RST}') + f' entering server-mode {curtimestr}{Clr.RST}' + ) logging.info(startupmsg) if sessiontype is FreeForAllSession: appcfg['Free-for-All Playlist Selection'] = self._playlist_name - appcfg['Free-for-All Playlist Randomize'] = ( - self._config.playlist_shuffle) + appcfg[ + 'Free-for-All Playlist Randomize' + ] = self._config.playlist_shuffle elif sessiontype is DualTeamSession: appcfg['Team Tournament Playlist Selection'] = self._playlist_name - appcfg['Team Tournament Playlist Randomize'] = ( - self._config.playlist_shuffle) + appcfg[ + 'Team Tournament Playlist Randomize' + ] = self._config.playlist_shuffle elif sessiontype is CoopSession: app.coop_session_args = { 'campaign': self._config.coop_campaign, @@ -363,7 +412,8 @@ class ServerController: _ba.set_authenticate_clients(self._config.authenticate_clients) _ba.set_enable_default_kick_voting( - self._config.enable_default_kick_voting) + self._config.enable_default_kick_voting + ) _ba.set_admins(self._config.admins) # Call set-enabled last (will push state to the cloud). @@ -376,10 +426,13 @@ class ServerController: if self._config.stress_test_players is not None: # Special case: run a stress test. from ba.internal import run_stress_test - run_stress_test(playlist_type='Random', - playlist_name='__default__', - player_count=self._config.stress_test_players, - round_duration=30) + + run_stress_test( + playlist_type='Random', + playlist_name='__default__', + player_count=self._config.stress_test_players, + round_duration=30, + ) else: _ba.new_host_session(sessiontype) diff --git a/dist/ba_data/python/ba/_session.py b/dist/ba_data/python/ba/_session.py index 02309f4..11f653c 100644 --- a/dist/ba_data/python/ba/_session.py +++ b/dist/ba_data/python/ba/_session.py @@ -70,12 +70,14 @@ class Session: """All the ba.SessionTeams in the Session. Most things should use the list of ba.Team-s in ba.Activity; not this.""" - def __init__(self, - depsets: Sequence[ba.DependencySet], - team_names: Sequence[str] | None = None, - team_colors: Sequence[Sequence[float]] | None = None, - min_players: int = 1, - max_players: int = 8): + def __init__( + self, + depsets: Sequence[ba.DependencySet], + team_names: Sequence[str] | None = None, + team_colors: Sequence[Sequence[float]] | None = None, + min_players: int = 1, + max_players: int = 8, + ): """Instantiate a session. depsets should be a sequence of successfully resolved ba.DependencySet @@ -116,10 +118,12 @@ class Session: # Throw a combined exception if we found anything missing. if missing_asset_packages: - raise DependencyError([ - Dependency(AssetPackage, set_id) - for set_id in missing_asset_packages - ]) + raise DependencyError( + [ + Dependency(AssetPackage, set_id) + for set_id in missing_asset_packages + ] + ) # Ok; looks like our dependencies check out. # Now give the engine a list of asset-set-ids to pass along to clients. @@ -152,27 +156,33 @@ class Session: self._wants_to_end = False self._ending = False self._activity_should_end_immediately = False - self._activity_should_end_immediately_results: (ba.GameResults - | None) = None + self._activity_should_end_immediately_results: ( + ba.GameResults | None + ) = None self._activity_should_end_immediately_delay = 0.0 # Create static teams if we're using them. if self.use_teams: if team_names is None: raise RuntimeError( - 'use_teams is True but team_names not provided.') + 'use_teams is True but team_names not provided.' + ) if team_colors is None: raise RuntimeError( - 'use_teams is True but team_colors not provided.') + 'use_teams is True but team_colors not provided.' + ) if len(team_colors) != len(team_names): - raise RuntimeError(f'Got {len(team_names)} team_names' - f' and {len(team_colors)} team_colors;' - f' these numbers must match.') + raise RuntimeError( + f'Got {len(team_names)} team_names' + f' and {len(team_colors)} team_colors;' + f' these numbers must match.' + ) for i, color in enumerate(team_colors): - team = SessionTeam(team_id=self._next_team_id, - name=GameActivity.get_team_display_string( - team_names[i]), - color=color) + team = SessionTeam( + team_id=self._next_team_id, + name=GameActivity.get_team_display_string(team_names[i]), + color=color, + ) self.sessionteams.append(team) self._next_team_id += 1 try: @@ -218,12 +228,15 @@ class Session: # Print a rejection message *only* to the client trying to # join (prevents spamming everyone else in the game). _ba.playsound(_ba.getsound('error')) - _ba.screenmessage(Lstr(resource='playerLimitReachedText', - subs=[('${COUNT}', - str(self.max_players))]), - color=(0.8, 0.0, 0.0), - clients=[player.inputdevice.client_id], - transient=True) + _ba.screenmessage( + Lstr( + resource='playerLimitReachedText', + subs=[('${COUNT}', str(self.max_players))], + ), + color=(0.8, 0.0, 0.0), + clients=[player.inputdevice.client_id], + transient=True, + ) return False _ba.playsound(_ba.getsound('dripity')) @@ -233,8 +246,10 @@ class Session: """Called when a previously-accepted ba.SessionPlayer leaves.""" if sessionplayer not in self.sessionplayers: - print('ERROR: Session.on_player_leave called' - ' for player not in our list.') + print( + 'ERROR: Session.on_player_leave called' + ' for player not in our list.' + ) return _ba.playsound(_ba.getsound('playerLeft')) @@ -256,15 +271,20 @@ class Session: assert sessionteam is not None _ba.screenmessage( - Lstr(resource='playerLeftText', - subs=[('${PLAYER}', sessionplayer.getname(full=True))])) + Lstr( + resource='playerLeftText', + subs=[('${PLAYER}', sessionplayer.getname(full=True))], + ) + ) # Remove them from their SessionTeam. if sessionplayer in sessionteam.players: sessionteam.players.remove(sessionplayer) else: - print('SessionPlayer not found in SessionTeam' - ' in on_player_leave.') + print( + 'SessionPlayer not found in SessionTeam' + ' in on_player_leave.' + ) # Grab their activity-specific player instance. player = sessionplayer.activityplayer @@ -284,8 +304,9 @@ class Session: # Now remove them from the session list. self.sessionplayers.remove(sessionplayer) - def _remove_player_team(self, sessionteam: ba.SessionTeam, - activity: ba.Activity | None) -> None: + def _remove_player_team( + self, sessionteam: ba.SessionTeam, activity: ba.Activity | None + ) -> None: """Remove the player-specific team in non-teams mode.""" # They should have been the only one on their team. @@ -306,14 +327,17 @@ class Session: self.on_team_leave(sessionteam) except Exception: print_exception( - f'Error in on_team_leave for Session {self}.') + f'Error in on_team_leave for Session {self}.' + ) else: print('Team no in Session teams in on_player_leave.') try: sessionteam.leave() except Exception: - print_exception(f'Error clearing sessiondata' - f' for team {sessionteam} in session {self}.') + print_exception( + f'Error clearing sessiondata' + f' for team {sessionteam} in session {self}.' + ) def end(self) -> None: """Initiates an end to the session and a return to the main menu. @@ -329,17 +353,20 @@ class Session: """(internal)""" from ba._activitytypes import EndSessionActivity from ba._generated.enums import TimeType + with _ba.Context(self): curtime = _ba.time(TimeType.REAL) if self._ending: # Ignore repeats unless its been a while. assert self._launch_end_session_activity_time is not None - since_last = (curtime - self._launch_end_session_activity_time) + since_last = curtime - self._launch_end_session_activity_time if since_last < 30.0: return print_error( - '_launch_end_session_activity called twice (since_last=' + - str(since_last) + ')') + '_launch_end_session_activity called twice (since_last=' + + str(since_last) + + ')' + ) self._launch_end_session_activity_time = curtime self.setactivity(_ba.newactivity(EndSessionActivity)) self._wants_to_end = False @@ -351,8 +378,9 @@ class Session: def on_team_leave(self, team: ba.SessionTeam) -> None: """Called when a ba.Team is leaving the session.""" - def end_activity(self, activity: ba.Activity, results: Any, delay: float, - force: bool) -> None: + def end_activity( + self, activity: ba.Activity, results: Any, delay: float, force: bool + ) -> None: """Commence shutdown of a ba.Activity (if not already occurring). 'delay' is the time delay before the Activity actually ends @@ -385,7 +413,8 @@ class Session: self._activity_end_timer = _ba.Timer( delay, Call(self._complete_end_activity, activity, results), - timetype=TimeType.BASE) + timetype=TimeType.BASE, + ) def handlemessage(self, msg: Any) -> Any: """General message handling; can be passed any message object.""" @@ -407,7 +436,6 @@ class Session: return None class _SetActivityScopedLock: - def __init__(self, session: ba.Session) -> None: self._session = session if session._in_set_activity: @@ -442,12 +470,16 @@ class Session: return if self._next_activity is not None: - raise RuntimeError('Activity switch already in progress (to ' + - str(self._next_activity) + ')') + raise RuntimeError( + 'Activity switch already in progress (to ' + + str(self._next_activity) + + ')' + ) prev_activity = self._activity_retained - prev_globals = (prev_activity.globalsnode - if prev_activity is not None else None) + prev_globals = ( + prev_activity.globalsnode if prev_activity is not None else None + ) # Let the activity do its thing. activity.transition_in(prev_globals) @@ -476,9 +508,11 @@ class Session: # will trigger the next activity to run). if prev_activity is not None: with _ba.Context('ui'): - _ba.timer(max(0.0, activity.transition_time), - prev_activity.expire, - timetype=TimeType.REAL) + _ba.timer( + max(0.0, activity.transition_time), + prev_activity.expire, + timetype=TimeType.REAL, + ) self._in_set_activity = False def getactivity(self) -> ba.Activity | None: @@ -495,15 +529,18 @@ class Session: """ return [] - def _complete_end_activity(self, activity: ba.Activity, - results: Any) -> None: + def _complete_end_activity( + self, activity: ba.Activity, results: Any + ) -> None: # Run the subclass callback in the session context. try: with _ba.Context(self): self.on_activity_end(activity, results) except Exception: - print_exception(f'Error in on_activity_end() for session {self}' - f' activity {activity} with results {results}') + print_exception( + f'Error in on_activity_end() for session {self}' + f' activity {activity} with results {results}' + ) def _request_player(self, sessionplayer: ba.SessionPlayer) -> bool: """Called by the native layer when a player wants to join.""" @@ -571,7 +608,8 @@ class Session: if self._activity_should_end_immediately: self._activity_retained.end( self._activity_should_end_immediately_results, - self._activity_should_end_immediately_delay) + self._activity_should_end_immediately_delay, + ) def _on_player_ready(self, chooser: ba.Chooser) -> None: """Called when a ba.Player has checked themself ready.""" @@ -601,8 +639,10 @@ class Session: self._complete_end_activity(activity, {}) else: _ba.screenmessage( - Lstr(resource='notEnoughPlayersText', - subs=[('${COUNT}', str(min_players))]), + Lstr( + resource='notEnoughPlayersText', + subs=[('${COUNT}', str(min_players))], + ), color=(1, 1, 0), ) _ba.playsound(_ba.getsound('error')) @@ -613,7 +653,8 @@ class Session: lobby.remove_chooser(chooser.getplayer()) def transitioning_out_activity_was_freed( - self, can_show_ad_on_death: bool) -> None: + self, can_show_ad_on_death: bool + ) -> None: """(internal)""" # pylint: disable=cyclic-import from ba._apputils import garbage_collect @@ -632,10 +673,12 @@ class Session: def _add_chosen_player(self, chooser: ba.Chooser) -> ba.SessionPlayer: from ba._team import SessionTeam + sessionplayer = chooser.getplayer() assert sessionplayer in self.sessionplayers, ( 'SessionPlayer not found in session ' - 'player-list after chooser selection.') + 'player-list after chooser selection.' + ) activity = self._activity_weak() assert activity is not None @@ -646,20 +689,26 @@ class Session: # We can pass it to the current activity if it has already begun # (otherwise it'll get passed once begin is called). - pass_to_activity = (activity.has_begun() - and not activity.is_joining_activity) + pass_to_activity = ( + activity.has_begun() and not activity.is_joining_activity + ) # However, if we're not allowing mid-game joins, don't actually pass; # just announce the arrival and say they'll partake next round. if pass_to_activity: - if not (activity.allow_mid_activity_joins - and self.should_allow_mid_activity_joins(activity)): + if not ( + activity.allow_mid_activity_joins + and self.should_allow_mid_activity_joins(activity) + ): pass_to_activity = False with _ba.Context(self): _ba.screenmessage( - Lstr(resource='playerDelayedJoinText', - subs=[('${PLAYER}', - sessionplayer.getname(full=True))]), + Lstr( + resource='playerDelayedJoinText', + subs=[ + ('${PLAYER}', sessionplayer.getname(full=True)) + ], + ), color=(0, 1, 0), ) @@ -691,10 +740,12 @@ class Session: assert sessionplayer not in sessionteam.players sessionteam.players.append(sessionplayer) - sessionplayer.setdata(team=sessionteam, - character=chooser.get_character_name(), - color=chooser.get_color(), - highlight=chooser.get_highlight()) + sessionplayer.setdata( + team=sessionteam, + character=chooser.get_character_name(), + color=chooser.get_color(), + highlight=chooser.get_highlight(), + ) self.stats.register_sessionplayer(sessionplayer) if pass_to_activity: diff --git a/dist/ba_data/python/ba/_settings.py b/dist/ba_data/python/ba/_settings.py index cf590b5..3534fdb 100644 --- a/dist/ba_data/python/ba/_settings.py +++ b/dist/ba_data/python/ba/_settings.py @@ -28,6 +28,7 @@ class BoolSetting(Setting): Category: Settings Classes """ + default: bool @@ -37,6 +38,7 @@ class IntSetting(Setting): Category: Settings Classes """ + default: int min_value: int = 0 max_value: int = 9999 @@ -49,6 +51,7 @@ class FloatSetting(Setting): Category: Settings Classes """ + default: float min_value: float = 0.0 max_value: float = 9999.0 @@ -61,6 +64,7 @@ class ChoiceSetting(Setting): Category: Settings Classes """ + choices: list[tuple[str, Any]] @@ -70,6 +74,7 @@ class IntChoiceSetting(ChoiceSetting): Category: Settings Classes """ + default: int choices: list[tuple[str, int]] @@ -80,5 +85,6 @@ class FloatChoiceSetting(ChoiceSetting): Category: Settings Classes """ + default: float choices: list[tuple[str, float]] diff --git a/dist/ba_data/python/ba/_stats.py b/dist/ba_data/python/ba/_stats.py index 71f088d..0f68beb 100644 --- a/dist/ba_data/python/ba/_stats.py +++ b/dist/ba_data/python/ba/_stats.py @@ -9,8 +9,13 @@ from typing import TYPE_CHECKING from dataclasses import dataclass import _ba -from ba._error import (print_exception, print_error, SessionTeamNotFoundError, - SessionPlayerNotFoundError, NotFoundError) +from ba._error import ( + print_exception, + print_error, + SessionTeamNotFoundError, + SessionPlayerNotFoundError, + NotFoundError, +) if TYPE_CHECKING: import ba @@ -37,10 +42,16 @@ class PlayerRecord: still present (stats may be retained for players that leave mid-game) """ + character: str - def __init__(self, name: str, name_full: str, - sessionplayer: ba.SessionPlayer, stats: ba.Stats): + def __init__( + self, + name: str, + name_full: str, + sessionplayer: ba.SessionPlayer, + stats: ba.Stats, + ): self.name = name self.name_full = name_full self.score = 0 @@ -105,8 +116,9 @@ class PlayerRecord: return stats.getactivity() return None - def associate_with_sessionplayer(self, - sessionplayer: ba.SessionPlayer) -> None: + def associate_with_sessionplayer( + self, sessionplayer: ba.SessionPlayer + ) -> None: """Associate this entry with a ba.SessionPlayer.""" self._sessionteam = weakref.ref(sessionplayer.sessionteam) self.character = sessionplayer.character @@ -129,6 +141,7 @@ class PlayerRecord: # pylint: disable=too-many-statements from ba._language import Lstr from ba._general import Call + self._multi_kill_count += 1 stats = self._stats() assert stats @@ -169,16 +182,23 @@ class PlayerRecord: sound = stats.orchestrahitsound4 else: score = 100 - name = Lstr(resource='multiKillText', - subs=[('${COUNT}', str(self._multi_kill_count))]) + name = Lstr( + resource='multiKillText', + subs=[('${COUNT}', str(self._multi_kill_count))], + ) color = (1.0, 0.5, 0.0, 1) scale = 1.3 delay = 1.0 sound = stats.orchestrahitsound4 - def _apply(name2: Lstr, score2: int, showpoints2: bool, - color2: tuple[float, float, float, float], scale2: float, - sound2: ba.Sound | None) -> None: + def _apply( + name2: Lstr, + score2: int, + showpoints2: bool, + color2: tuple[float, float, float, float], + scale2: float, + sound2: ba.Sound | None, + ) -> None: from bastd.actor.popuptext import PopupText # Only award this if they're still alive and we can get @@ -194,18 +214,23 @@ class PlayerRecord: return # Jitter position a bit since these often come in clusters. - our_pos = _ba.Vec3(our_pos[0] + (random.random() - 0.5) * 2.0, - our_pos[1] + (random.random() - 0.5) * 2.0, - our_pos[2] + (random.random() - 0.5) * 2.0) + our_pos = _ba.Vec3( + our_pos[0] + (random.random() - 0.5) * 2.0, + our_pos[1] + (random.random() - 0.5) * 2.0, + our_pos[2] + (random.random() - 0.5) * 2.0, + ) activity = self.getactivity() if activity is not None: - PopupText(Lstr( - value=(('+' + str(score2) + ' ') if showpoints2 else '') + - '${N}', - subs=[('${N}', name2)]), - color=color2, - scale=scale2, - position=our_pos).autoretain() + PopupText( + Lstr( + value=(('+' + str(score2) + ' ') if showpoints2 else '') + + '${N}', + subs=[('${N}', name2)], + ), + color=color2, + scale=scale2, + position=our_pos, + ).autoretain() if sound2: _ba.playsound(sound2) @@ -219,7 +244,8 @@ class PlayerRecord: if name is not None: _ba.timer( 0.3 + delay, - Call(_apply, name, score, showpoints, color, scale, sound)) + Call(_apply, name, score, showpoints, color, scale, sound), + ) # Keep the tally rollin'... # set a timer for a bit in the future. @@ -296,8 +322,9 @@ class Stats: self._player_records[name].associate_with_sessionplayer(player) else: name_full = player.getname(full=True) - self._player_records[name] = PlayerRecord(name, name_full, player, - self) + self._player_records[name] = PlayerRecord( + name, name_full, player, self + ) def get_records(self) -> dict[str, ba.PlayerRecord]: """Get PlayerRecord corresponding to still-existing players.""" @@ -311,20 +338,22 @@ class Stats: records[record_id] = record return records - def player_scored(self, - player: ba.Player, - base_points: int = 1, - target: Sequence[float] | None = None, - kill: bool = False, - victim_player: ba.Player | None = None, - scale: float = 1.0, - color: Sequence[float] | None = None, - title: str | ba.Lstr | None = None, - screenmessage: bool = True, - display: bool = True, - importance: int = 1, - showpoints: bool = True, - big_message: bool = False) -> int: + def player_scored( + self, + player: ba.Player, + base_points: int = 1, + target: Sequence[float] | None = None, + kill: bool = False, + victim_player: ba.Player | None = None, + scale: float = 1.0, + color: Sequence[float] | None = None, + title: str | ba.Lstr | None = None, + screenmessage: bool = True, + display: bool = True, + importance: int = 1, + showpoints: bool = True, + big_message: bool = False, + ) -> int: """Register a score for the player. Return value is actual score with multipliers and such factored in. @@ -338,6 +367,7 @@ class Stats: from ba import _math from ba._gameactivity import GameActivity from ba._language import Lstr + del victim_player # Currently unused. name = player.getname() s_player = self._player_records[name] @@ -361,9 +391,12 @@ class Stats: if isinstance(activity, GameActivity): name_full = player.getname(full=True, icon=False) activity.show_zoom_message( - Lstr(resource='nameScoresText', - subs=[('${NAME}', name_full)]), - color=_math.normalized_color(player.team.color)) + Lstr( + resource='nameScoresText', + subs=[('${NAME}', name_full)], + ), + color=_math.normalized_color(player.team.color), + ) except Exception: print_exception('error showing big_message') @@ -376,21 +409,26 @@ class Stats: # If display-pos is *way* lower than us, raise it up # (so we can still see scores from dudes that fell off cliffs). - display_pos = (target[0], max(target[1], our_pos[1] - 2.0), - min(target[2], our_pos[2] + 2.0)) + display_pos = ( + target[0], + max(target[1], our_pos[1] - 2.0), + min(target[2], our_pos[2] + 2.0), + ) activity = self.getactivity() if activity is not None: if title is not None: - sval = Lstr(value='+${A} ${B}', - subs=[('${A}', str(points)), - ('${B}', title)]) + sval = Lstr( + value='+${A} ${B}', + subs=[('${A}', str(points)), ('${B}', title)], + ) else: - sval = Lstr(value='+${A}', - subs=[('${A}', str(points))]) - PopupText(sval, - color=display_color, - scale=1.2 * scale, - position=display_pos).autoretain() + sval = Lstr(value='+${A}', subs=[('${A}', str(points))]) + PopupText( + sval, + color=display_color, + scale=1.2 * scale, + position=display_pos, + ).autoretain() # Tally kills. if kill: @@ -400,11 +438,12 @@ class Stats: # Report non-kill scorings. try: if screenmessage and not kill: - _ba.screenmessage(Lstr(resource='nameScoresText', - subs=[('${NAME}', name)]), - top=True, - color=player.color, - image=player.get_icon()) + _ba.screenmessage( + Lstr(resource='nameScoresText', subs=[('${NAME}', name)]), + top=True, + color=player.color, + image=player.get_icon(), + ) except Exception: print_exception('error announcing score') @@ -419,12 +458,15 @@ class Stats: return points - def player_was_killed(self, - player: ba.Player, - killed: bool = False, - killer: ba.Player | None = None) -> None: + def player_was_killed( + self, + player: ba.Player, + killed: bool = False, + killer: ba.Player | None = None, + ) -> None: """Should be called when a player is killed.""" from ba._language import Lstr + name = player.getname() prec = self._player_records[name] prec.streak = 0 @@ -434,33 +476,47 @@ class Stats: try: if killed and _ba.getactivity().announce_player_deaths: if killer is player: - _ba.screenmessage(Lstr(resource='nameSuicideText', - subs=[('${NAME}', name)]), - top=True, - color=player.color, - image=player.get_icon()) + _ba.screenmessage( + Lstr( + resource='nameSuicideText', subs=[('${NAME}', name)] + ), + top=True, + color=player.color, + image=player.get_icon(), + ) elif killer is not None: if killer.team is player.team: - _ba.screenmessage(Lstr(resource='nameBetrayedText', - subs=[('${NAME}', - killer.getname()), - ('${VICTIM}', name)]), - top=True, - color=killer.color, - image=killer.get_icon()) + _ba.screenmessage( + Lstr( + resource='nameBetrayedText', + subs=[ + ('${NAME}', killer.getname()), + ('${VICTIM}', name), + ], + ), + top=True, + color=killer.color, + image=killer.get_icon(), + ) else: - _ba.screenmessage(Lstr(resource='nameKilledText', - subs=[('${NAME}', - killer.getname()), - ('${VICTIM}', name)]), - top=True, - color=killer.color, - image=killer.get_icon()) + _ba.screenmessage( + Lstr( + resource='nameKilledText', + subs=[ + ('${NAME}', killer.getname()), + ('${VICTIM}', name), + ], + ), + top=True, + color=killer.color, + image=killer.get_icon(), + ) else: - _ba.screenmessage(Lstr(resource='nameDiedText', - subs=[('${NAME}', name)]), - top=True, - color=player.color, - image=player.get_icon()) + _ba.screenmessage( + Lstr(resource='nameDiedText', subs=[('${NAME}', name)]), + top=True, + color=player.color, + image=player.get_icon(), + ) except Exception: print_exception('error announcing kill') diff --git a/dist/ba_data/python/ba/_store.py b/dist/ba_data/python/ba/_store.py index f56b0a8..850a0aa 100644 --- a/dist/ba_data/python/ba/_store.py +++ b/dist/ba_data/python/ba/_store.py @@ -24,14 +24,17 @@ def get_store_item_name_translated(item_name: str) -> ba.Lstr: # pylint: disable=cyclic-import from ba import _language from ba import _map + item_info = get_store_item(item_name) if item_name.startswith('characters.'): - return _language.Lstr(translate=('characterNames', - item_info['character'])) + return _language.Lstr( + translate=('characterNames', item_info['character']) + ) if item_name in ['upgrades.pro', 'pro']: - return _language.Lstr(resource='store.bombSquadProNameText', - subs=[('${APP_NAME}', - _language.Lstr(resource='titleText'))]) + return _language.Lstr( + resource='store.bombSquadProNameText', + subs=[('${APP_NAME}', _language.Lstr(resource='titleText'))], + ) if item_name.startswith('maps.'): map_type: type[ba.Map] = item_info['map_type'] return _map.get_map_display_string(map_type.name) @@ -64,6 +67,7 @@ def get_store_items() -> dict[str, dict]: # pylint: disable=cyclic-import from ba._generated.enums import SpecialChar from bastd import maps + if _ba.app.store_items is None: from bastd.game import ninjafight from bastd.game import meteorshower @@ -73,114 +77,64 @@ def get_store_items() -> dict[str, dict]: # IMPORTANT - need to keep this synced with the master server. # (doing so manually for now) _ba.app.store_items = { - 'characters.kronk': { - 'character': 'Kronk' - }, - 'characters.zoe': { - 'character': 'Zoe' - }, - 'characters.jackmorgan': { - 'character': 'Jack Morgan' - }, - 'characters.mel': { - 'character': 'Mel' - }, - 'characters.snakeshadow': { - 'character': 'Snake Shadow' - }, - 'characters.bones': { - 'character': 'Bones' - }, + 'characters.kronk': {'character': 'Kronk'}, + 'characters.zoe': {'character': 'Zoe'}, + 'characters.jackmorgan': {'character': 'Jack Morgan'}, + 'characters.mel': {'character': 'Mel'}, + 'characters.snakeshadow': {'character': 'Snake Shadow'}, + 'characters.bones': {'character': 'Bones'}, 'characters.bernard': { 'character': 'Bernard', - 'highlight': (0.6, 0.5, 0.8) - }, - 'characters.pixie': { - 'character': 'Pixel' - }, - 'characters.wizard': { - 'character': 'Grumbledorf' - }, - 'characters.frosty': { - 'character': 'Frosty' - }, - 'characters.pascal': { - 'character': 'Pascal' - }, - 'characters.cyborg': { - 'character': 'B-9000' - }, - 'characters.agent': { - 'character': 'Agent Johnson' - }, - 'characters.taobaomascot': { - 'character': 'Taobao Mascot' - }, - 'characters.santa': { - 'character': 'Santa Claus' - }, - 'characters.bunny': { - 'character': 'Easter Bunny' + 'highlight': (0.6, 0.5, 0.8), }, + 'characters.pixie': {'character': 'Pixel'}, + 'characters.wizard': {'character': 'Grumbledorf'}, + 'characters.frosty': {'character': 'Frosty'}, + 'characters.pascal': {'character': 'Pascal'}, + 'characters.cyborg': {'character': 'B-9000'}, + 'characters.agent': {'character': 'Agent Johnson'}, + 'characters.taobaomascot': {'character': 'Taobao Mascot'}, + 'characters.santa': {'character': 'Santa Claus'}, + 'characters.bunny': {'character': 'Easter Bunny'}, 'pro': {}, - 'maps.lake_frigid': { - 'map_type': maps.LakeFrigid - }, + 'maps.lake_frigid': {'map_type': maps.LakeFrigid}, 'games.ninja_fight': { 'gametype': ninjafight.NinjaFightGame, - 'previewTex': 'courtyardPreview' + 'previewTex': 'courtyardPreview', }, 'games.meteor_shower': { 'gametype': meteorshower.MeteorShowerGame, - 'previewTex': 'rampagePreview' + 'previewTex': 'rampagePreview', }, 'games.target_practice': { 'gametype': targetpractice.TargetPracticeGame, - 'previewTex': 'doomShroomPreview' + 'previewTex': 'doomShroomPreview', }, 'games.easter_egg_hunt': { 'gametype': easteregghunt.EasterEggHuntGame, - 'previewTex': 'towerDPreview' + 'previewTex': 'towerDPreview', }, 'icons.flag_us': { 'icon': _ba.charstr(SpecialChar.FLAG_UNITED_STATES) }, - 'icons.flag_mexico': { - 'icon': _ba.charstr(SpecialChar.FLAG_MEXICO) - }, + 'icons.flag_mexico': {'icon': _ba.charstr(SpecialChar.FLAG_MEXICO)}, 'icons.flag_germany': { 'icon': _ba.charstr(SpecialChar.FLAG_GERMANY) }, - 'icons.flag_brazil': { - 'icon': _ba.charstr(SpecialChar.FLAG_BRAZIL) - }, - 'icons.flag_russia': { - 'icon': _ba.charstr(SpecialChar.FLAG_RUSSIA) - }, - 'icons.flag_china': { - 'icon': _ba.charstr(SpecialChar.FLAG_CHINA) - }, + 'icons.flag_brazil': {'icon': _ba.charstr(SpecialChar.FLAG_BRAZIL)}, + 'icons.flag_russia': {'icon': _ba.charstr(SpecialChar.FLAG_RUSSIA)}, + 'icons.flag_china': {'icon': _ba.charstr(SpecialChar.FLAG_CHINA)}, 'icons.flag_uk': { 'icon': _ba.charstr(SpecialChar.FLAG_UNITED_KINGDOM) }, - 'icons.flag_canada': { - 'icon': _ba.charstr(SpecialChar.FLAG_CANADA) - }, - 'icons.flag_india': { - 'icon': _ba.charstr(SpecialChar.FLAG_INDIA) - }, - 'icons.flag_japan': { - 'icon': _ba.charstr(SpecialChar.FLAG_JAPAN) - }, - 'icons.flag_france': { - 'icon': _ba.charstr(SpecialChar.FLAG_FRANCE) - }, + 'icons.flag_canada': {'icon': _ba.charstr(SpecialChar.FLAG_CANADA)}, + 'icons.flag_india': {'icon': _ba.charstr(SpecialChar.FLAG_INDIA)}, + 'icons.flag_japan': {'icon': _ba.charstr(SpecialChar.FLAG_JAPAN)}, + 'icons.flag_france': {'icon': _ba.charstr(SpecialChar.FLAG_FRANCE)}, 'icons.flag_indonesia': { 'icon': _ba.charstr(SpecialChar.FLAG_INDONESIA) }, - 'icons.flag_italy': { - 'icon': _ba.charstr(SpecialChar.FLAG_ITALY) - }, + 'icons.flag_italy': {'icon': _ba.charstr(SpecialChar.FLAG_ITALY)}, 'icons.flag_south_korea': { 'icon': _ba.charstr(SpecialChar.FLAG_SOUTH_KOREA) }, @@ -190,15 +144,9 @@ def get_store_items() -> dict[str, dict]: 'icons.flag_uae': { 'icon': _ba.charstr(SpecialChar.FLAG_UNITED_ARAB_EMIRATES) }, - 'icons.flag_qatar': { - 'icon': _ba.charstr(SpecialChar.FLAG_QATAR) - }, - 'icons.flag_egypt': { - 'icon': _ba.charstr(SpecialChar.FLAG_EGYPT) - }, - 'icons.flag_kuwait': { - 'icon': _ba.charstr(SpecialChar.FLAG_KUWAIT) - }, + 'icons.flag_qatar': {'icon': _ba.charstr(SpecialChar.FLAG_QATAR)}, + 'icons.flag_egypt': {'icon': _ba.charstr(SpecialChar.FLAG_EGYPT)}, + 'icons.flag_kuwait': {'icon': _ba.charstr(SpecialChar.FLAG_KUWAIT)}, 'icons.flag_algeria': { 'icon': _ba.charstr(SpecialChar.FLAG_ALGERIA) }, @@ -217,69 +165,33 @@ def get_store_items() -> dict[str, dict]: 'icons.flag_singapore': { 'icon': _ba.charstr(SpecialChar.FLAG_SINGAPORE) }, - 'icons.flag_iran': { - 'icon': _ba.charstr(SpecialChar.FLAG_IRAN) - }, - 'icons.flag_poland': { - 'icon': _ba.charstr(SpecialChar.FLAG_POLAND) - }, + 'icons.flag_iran': {'icon': _ba.charstr(SpecialChar.FLAG_IRAN)}, + 'icons.flag_poland': {'icon': _ba.charstr(SpecialChar.FLAG_POLAND)}, 'icons.flag_argentina': { 'icon': _ba.charstr(SpecialChar.FLAG_ARGENTINA) }, 'icons.flag_philippines': { 'icon': _ba.charstr(SpecialChar.FLAG_PHILIPPINES) }, - 'icons.flag_chile': { - 'icon': _ba.charstr(SpecialChar.FLAG_CHILE) - }, - 'icons.fedora': { - 'icon': _ba.charstr(SpecialChar.FEDORA) - }, - 'icons.hal': { - 'icon': _ba.charstr(SpecialChar.HAL) - }, - 'icons.crown': { - 'icon': _ba.charstr(SpecialChar.CROWN) - }, - 'icons.yinyang': { - 'icon': _ba.charstr(SpecialChar.YIN_YANG) - }, - 'icons.eyeball': { - 'icon': _ba.charstr(SpecialChar.EYE_BALL) - }, - 'icons.skull': { - 'icon': _ba.charstr(SpecialChar.SKULL) - }, - 'icons.heart': { - 'icon': _ba.charstr(SpecialChar.HEART) - }, - 'icons.dragon': { - 'icon': _ba.charstr(SpecialChar.DRAGON) - }, - 'icons.helmet': { - 'icon': _ba.charstr(SpecialChar.HELMET) - }, - 'icons.mushroom': { - 'icon': _ba.charstr(SpecialChar.MUSHROOM) - }, - 'icons.ninja_star': { - 'icon': _ba.charstr(SpecialChar.NINJA_STAR) - }, + 'icons.flag_chile': {'icon': _ba.charstr(SpecialChar.FLAG_CHILE)}, + 'icons.fedora': {'icon': _ba.charstr(SpecialChar.FEDORA)}, + 'icons.hal': {'icon': _ba.charstr(SpecialChar.HAL)}, + 'icons.crown': {'icon': _ba.charstr(SpecialChar.CROWN)}, + 'icons.yinyang': {'icon': _ba.charstr(SpecialChar.YIN_YANG)}, + 'icons.eyeball': {'icon': _ba.charstr(SpecialChar.EYE_BALL)}, + 'icons.skull': {'icon': _ba.charstr(SpecialChar.SKULL)}, + 'icons.heart': {'icon': _ba.charstr(SpecialChar.HEART)}, + 'icons.dragon': {'icon': _ba.charstr(SpecialChar.DRAGON)}, + 'icons.helmet': {'icon': _ba.charstr(SpecialChar.HELMET)}, + 'icons.mushroom': {'icon': _ba.charstr(SpecialChar.MUSHROOM)}, + 'icons.ninja_star': {'icon': _ba.charstr(SpecialChar.NINJA_STAR)}, 'icons.viking_helmet': { 'icon': _ba.charstr(SpecialChar.VIKING_HELMET) }, - 'icons.moon': { - 'icon': _ba.charstr(SpecialChar.MOON) - }, - 'icons.spider': { - 'icon': _ba.charstr(SpecialChar.SPIDER) - }, - 'icons.fireball': { - 'icon': _ba.charstr(SpecialChar.FIREBALL) - }, - 'icons.mikirog': { - 'icon': _ba.charstr(SpecialChar.MIKIROG) - }, + 'icons.moon': {'icon': _ba.charstr(SpecialChar.MOON)}, + 'icons.spider': {'icon': _ba.charstr(SpecialChar.SPIDER)}, + 'icons.fireball': {'icon': _ba.charstr(SpecialChar.FIREBALL)}, + 'icons.mikirog': {'icon': _ba.charstr(SpecialChar.MIKIROG)}, } store_items = _ba.app.store_items assert store_items is not None @@ -289,97 +201,107 @@ def get_store_items() -> dict[str, dict]: def get_store_layout() -> dict[str, list[dict[str, Any]]]: """Return what's available in the store at a given time. - Categorized by tab and by section.""" + Categorized by tab and by section.""" if _ba.app.store_layout is None: _ba.app.store_layout = { - 'characters': [{ - 'items': [] - }], - 'extras': [{ - 'items': ['pro'] - }], - 'maps': [{ - 'items': ['maps.lake_frigid'] - }], + 'characters': [{'items': []}], + 'extras': [{'items': ['pro']}], + 'maps': [{'items': ['maps.lake_frigid']}], 'minigames': [], - 'icons': [{ - 'items': [ - 'icons.mushroom', - 'icons.heart', - 'icons.eyeball', - 'icons.yinyang', - 'icons.hal', - 'icons.flag_us', - 'icons.flag_mexico', - 'icons.flag_germany', - 'icons.flag_brazil', - 'icons.flag_russia', - 'icons.flag_china', - 'icons.flag_uk', - 'icons.flag_canada', - 'icons.flag_india', - 'icons.flag_japan', - 'icons.flag_france', - 'icons.flag_indonesia', - 'icons.flag_italy', - 'icons.flag_south_korea', - 'icons.flag_netherlands', - 'icons.flag_uae', - 'icons.flag_qatar', - 'icons.flag_egypt', - 'icons.flag_kuwait', - 'icons.flag_algeria', - 'icons.flag_saudi_arabia', - 'icons.flag_malaysia', - 'icons.flag_czech_republic', - 'icons.flag_australia', - 'icons.flag_singapore', - 'icons.flag_iran', - 'icons.flag_poland', - 'icons.flag_argentina', - 'icons.flag_philippines', - 'icons.flag_chile', - 'icons.moon', - 'icons.fedora', - 'icons.spider', - 'icons.ninja_star', - 'icons.skull', - 'icons.dragon', - 'icons.viking_helmet', - 'icons.fireball', - 'icons.helmet', - 'icons.crown', - ] - }] + 'icons': [ + { + 'items': [ + 'icons.mushroom', + 'icons.heart', + 'icons.eyeball', + 'icons.yinyang', + 'icons.hal', + 'icons.flag_us', + 'icons.flag_mexico', + 'icons.flag_germany', + 'icons.flag_brazil', + 'icons.flag_russia', + 'icons.flag_china', + 'icons.flag_uk', + 'icons.flag_canada', + 'icons.flag_india', + 'icons.flag_japan', + 'icons.flag_france', + 'icons.flag_indonesia', + 'icons.flag_italy', + 'icons.flag_south_korea', + 'icons.flag_netherlands', + 'icons.flag_uae', + 'icons.flag_qatar', + 'icons.flag_egypt', + 'icons.flag_kuwait', + 'icons.flag_algeria', + 'icons.flag_saudi_arabia', + 'icons.flag_malaysia', + 'icons.flag_czech_republic', + 'icons.flag_australia', + 'icons.flag_singapore', + 'icons.flag_iran', + 'icons.flag_poland', + 'icons.flag_argentina', + 'icons.flag_philippines', + 'icons.flag_chile', + 'icons.moon', + 'icons.fedora', + 'icons.spider', + 'icons.ninja_star', + 'icons.skull', + 'icons.dragon', + 'icons.viking_helmet', + 'icons.fireball', + 'icons.helmet', + 'icons.crown', + ] + } + ], } store_layout = _ba.app.store_layout assert store_layout is not None - store_layout['characters'] = [{ - 'items': [ - 'characters.kronk', 'characters.zoe', 'characters.jackmorgan', - 'characters.mel', 'characters.snakeshadow', 'characters.bones', - 'characters.bernard', 'characters.agent', 'characters.frosty', - 'characters.pascal', 'characters.pixie' - ] - }] - store_layout['minigames'] = [{ - 'items': [ - 'games.ninja_fight', 'games.meteor_shower', 'games.target_practice' - ] - }] + store_layout['characters'] = [ + { + 'items': [ + 'characters.kronk', + 'characters.zoe', + 'characters.jackmorgan', + 'characters.mel', + 'characters.snakeshadow', + 'characters.bones', + 'characters.bernard', + 'characters.agent', + 'characters.frosty', + 'characters.pascal', + 'characters.pixie', + ] + } + ] + store_layout['minigames'] = [ + { + 'items': [ + 'games.ninja_fight', + 'games.meteor_shower', + 'games.target_practice', + ] + } + ] if _internal.get_v1_account_misc_read_val('xmas', False): store_layout['characters'][0]['items'].append('characters.santa') store_layout['characters'][0]['items'].append('characters.wizard') store_layout['characters'][0]['items'].append('characters.cyborg') if _internal.get_v1_account_misc_read_val('easter', False): - store_layout['characters'].append({ - 'title': 'store.holidaySpecialText', - 'items': ['characters.bunny'] - }) - store_layout['minigames'].append({ - 'title': 'store.holidaySpecialText', - 'items': ['games.easter_egg_hunt'] - }) + store_layout['characters'].append( + {'title': 'store.holidaySpecialText', 'items': ['characters.bunny']} + ) + store_layout['minigames'].append( + { + 'title': 'store.holidaySpecialText', + 'items': ['games.easter_egg_hunt'], + } + ) return store_layout @@ -394,7 +316,7 @@ def get_clean_price(price_string: str) -> str: '$4.99': '$5.00', '$9.99': '$10.00', '$19.99': '$20.00', - '$49.99': '$50.00' + '$49.99': '$50.00', } return psubs.get(price_string, price_string) @@ -418,19 +340,23 @@ def get_available_purchase_count(tab: str | None = None) -> int: return count except Exception: from ba import _error + _error.print_exception('error calcing available purchases') return 0 -def _calc_count_for_tab(tabval: list[dict[str, Any]], our_tickets: int, - count: int) -> int: +def _calc_count_for_tab( + tabval: list[dict[str, Any]], our_tickets: int, count: int +) -> int: for section in tabval: for item in section['items']: ticket_cost = _internal.get_v1_account_misc_read_val( - 'price.' + item, None) + 'price.' + item, None + ) if ticket_cost is not None: - if (our_tickets >= ticket_cost - and not _internal.get_purchased(item)): + if our_tickets >= ticket_cost and not _internal.get_purchased( + item + ): count += 1 return count @@ -443,6 +369,7 @@ def get_available_sale_time(tab: str) -> int | None: try: import datetime from ba._generated.enums import TimeType, TimeFormat + app = _ba.app sale_times: list[int | None] = [] @@ -458,18 +385,21 @@ def get_available_sale_time(tab: str) -> int | None: # If we've got a time-remaining in our config, start there. if 'PSTR' in config: app.pro_sale_start_time = int( - _ba.time(TimeType.REAL, TimeFormat.MILLISECONDS)) + _ba.time(TimeType.REAL, TimeFormat.MILLISECONDS) + ) app.pro_sale_start_val = config['PSTR'] else: # We start the timer once we get the duration from # the server. start_duration = _internal.get_v1_account_misc_read_val( - 'proSaleDurationMinutes', None) + 'proSaleDurationMinutes', None + ) if start_duration is not None: app.pro_sale_start_time = int( - _ba.time(TimeType.REAL, TimeFormat.MILLISECONDS)) - app.pro_sale_start_val = (60000 * start_duration) + _ba.time(TimeType.REAL, TimeFormat.MILLISECONDS) + ) + app.pro_sale_start_val = 60000 * start_duration # If we haven't heard from the server yet, no sale.. else: @@ -477,9 +407,13 @@ def get_available_sale_time(tab: str) -> int | None: assert app.pro_sale_start_val is not None val: int | None = max( - 0, app.pro_sale_start_val - - (_ba.time(TimeType.REAL, TimeFormat.MILLISECONDS) - - app.pro_sale_start_time)) + 0, + app.pro_sale_start_val + - ( + _ba.time(TimeType.REAL, TimeFormat.MILLISECONDS) + - app.pro_sale_start_time + ), + ) # Keep the value in the config up to date. I suppose we should # write the config occasionally but it should happen often enough @@ -496,9 +430,12 @@ def get_available_sale_time(tab: str) -> int | None: for item in section['items']: if item in sales_raw: if not _internal.get_purchased(item): - to_end = ((datetime.datetime.utcfromtimestamp( - sales_raw[item]['e']) - - datetime.datetime.utcnow()).total_seconds()) + to_end = ( + datetime.datetime.utcfromtimestamp( + sales_raw[item]['e'] + ) + - datetime.datetime.utcnow() + ).total_seconds() if to_end > 0: sale_times.append(int(to_end * 1000)) @@ -508,6 +445,7 @@ def get_available_sale_time(tab: str) -> int | None: except Exception: from ba import _error + _error.print_exception('error calcing sale time') return None @@ -540,5 +478,6 @@ def get_unowned_game_types() -> set[type[ba.GameActivity]]: return unowned_games except Exception: from ba import _error + _error.print_exception('error calcing un-owned games') return set() diff --git a/dist/ba_data/python/ba/_team.py b/dist/ba_data/python/ba/_team.py index dac90ae..9e7390f 100644 --- a/dist/ba_data/python/ba/_team.py +++ b/dist/ba_data/python/ba/_team.py @@ -44,10 +44,12 @@ class SessionTeam: id: int """The unique numeric id of the team.""" - def __init__(self, - team_id: int = 0, - name: ba.Lstr | str = '', - color: Sequence[float] = (1.0, 1.0, 1.0)): + def __init__( + self, + team_id: int = 0, + name: ba.Lstr | str = '', + color: Sequence[float] = (1.0, 1.0, 1.0), + ): """Instantiate a ba.SessionTeam. In most cases, all teams are provided to you by the ba.Session, @@ -109,7 +111,8 @@ class Team(Generic[PlayerType]): f' operator (__eq__) which will break internal' f' logic. Please remove it.\n' f'For dataclasses you can do "dataclass(eq=False)"' - f' in the class decorator.') + f' in the class decorator.' + ) self.players = [] self._sessionteam = weakref.ref(sessionteam) @@ -120,8 +123,9 @@ class Team(Generic[PlayerType]): self._expired = False self._postinited = True - def manual_init(self, team_id: int, name: ba.Lstr | str, - color: tuple[float, ...]) -> None: + def manual_init( + self, team_id: int, name: ba.Lstr | str, color: tuple[float, ...] + ) -> None: """Manually init a team for uses such as bots.""" self.id = team_id self.name = name @@ -186,6 +190,7 @@ class Team(Generic[PlayerType]): if sessionteam is not None: return sessionteam from ba import _error + raise _error.SessionTeamNotFoundError() diff --git a/dist/ba_data/python/ba/_teamgame.py b/dist/ba_data/python/ba/_teamgame.py index ccf9418..5a6aaed 100644 --- a/dist/ba_data/python/ba/_teamgame.py +++ b/dist/ba_data/python/ba/_teamgame.py @@ -39,8 +39,9 @@ class TeamGameActivity(GameActivity[PlayerType, TeamType]): returns True for ba.DualTeamSessions and ba.FreeForAllSessions; False otherwise. """ - return (issubclass(sessiontype, DualTeamSession) - or issubclass(sessiontype, FreeForAllSession)) + return issubclass(sessiontype, DualTeamSession) or issubclass( + sessiontype, FreeForAllSession + ) def __init__(self, settings: dict): super().__init__(settings) @@ -55,6 +56,7 @@ class TeamGameActivity(GameActivity[PlayerType, TeamType]): # pylint: disable=cyclic-import from ba._coopsession import CoopSession from bastd.actor.controlsguide import ControlsGuide + super().on_transition_in() # On the first game, show the controls UI momentarily. @@ -67,11 +69,13 @@ class TeamGameActivity(GameActivity[PlayerType, TeamType]): lifespan = 10.0 if self.slow_motion: lifespan *= 0.3 - ControlsGuide(delay=delay, - lifespan=lifespan, - scale=0.8, - position=(380, 200), - bright=True).autoretain() + ControlsGuide( + delay=delay, + lifespan=lifespan, + scale=0.8, + position=(380, 200), + bright=True, + ).autoretain() setattr(self.session, attrname, True) def on_begin(self) -> None: @@ -84,15 +88,19 @@ class TeamGameActivity(GameActivity[PlayerType, TeamType]): elif isinstance(self.session, DualTeamSession): if len(self.players) >= 4: from ba import _achievement + _ba.app.ach.award_local_achievement('Team Player') except Exception: from ba import _error + _error.print_exception() - def spawn_player_spaz(self, - player: PlayerType, - position: Sequence[float] | None = None, - angle: float | None = None) -> PlayerSpaz: + def spawn_player_spaz( + self, + player: PlayerType, + position: Sequence[float] | None = None, + angle: float | None = None, + ) -> PlayerSpaz: """ Method override; spawns and wires up a standard ba.PlayerSpaz for a ba.Player. @@ -103,7 +111,7 @@ class TeamGameActivity(GameActivity[PlayerType, TeamType]): if position is None: # In teams-mode get our team-start-location. if isinstance(self.session, DualTeamSession): - position = (self.map.get_start_position(player.team.id)) + position = self.map.get_start_position(player.team.id) else: # Otherwise do free-for-all spawn locations. position = self.map.get_ffa_start_position(self.players) @@ -112,11 +120,12 @@ class TeamGameActivity(GameActivity[PlayerType, TeamType]): # FIXME: need to unify these arguments with GameActivity.end() def end( # type: ignore - self, - results: Any = None, - announce_winning_team: bool = True, - announce_delay: float = 0.1, - force: bool = False) -> None: + self, + results: Any = None, + announce_winning_team: bool = True, + announce_delay: float = 0.1, + force: bool = False, + ) -> None: """ End the game and announce the single winning team unless 'announce_winning_team' is False. @@ -141,15 +150,19 @@ class TeamGameActivity(GameActivity[PlayerType, TeamType]): self, results, delay=announce_delay, - announce_winning_team=announce_winning_team) + announce_winning_team=announce_winning_team, + ) # For co-op we just pass this up the chain with a delay added # (in most cases). Team games expect a delay for the announce # portion in teams/ffa mode so this keeps it consistent. else: # don't want delay on restarts.. - if (isinstance(results, dict) and 'outcome' in results - and results['outcome'] == 'restart'): + if ( + isinstance(results, dict) + and 'outcome' in results + and results['outcome'] == 'restart' + ): delay = 0.0 else: delay = 2.0 diff --git a/dist/ba_data/python/ba/_tips.py b/dist/ba_data/python/ba/_tips.py index c6e8955..604d825 100644 --- a/dist/ba_data/python/ba/_tips.py +++ b/dist/ba_data/python/ba/_tips.py @@ -27,53 +27,94 @@ def get_next_tip() -> str: def get_all_tips() -> list[str]: """Return the complete list of tips.""" tips = [ - ('If you are short on controllers, install the \'${REMOTE_APP_NAME}\' ' - 'app\non your mobile devices to use them as controllers.'), - ('Create player profiles for yourself and your friends with\nyour ' - 'preferred names and appearances instead of using random ones.'), - ('You can \'aim\' your punches by spinning left or right.\nThis is ' - 'useful for knocking bad guys off edges or scoring in hockey.'), - ('If you pick up a curse, your only hope for survival is to\nfind a ' - 'health powerup in the next few seconds.'), - ('A perfectly timed running-jumping-spin-punch can kill in a single ' - 'hit\nand earn you lifelong respect from your friends.'), + ( + 'If you are short on controllers,' + ' install the \'${REMOTE_APP_NAME}\' app\n' + 'on your mobile devices to use them as controllers.' + ), + ( + 'Create player profiles for yourself and your friends with\nyour ' + 'preferred names and appearances instead of using random ones.' + ), + ( + 'You can \'aim\' your punches by spinning left or right.\nThis is ' + 'useful for knocking bad guys off edges or scoring in hockey.' + ), + ( + 'If you pick up a curse, your only hope for survival is to\nfind a ' + 'health powerup in the next few seconds.' + ), + ( + 'A perfectly timed running-jumping-spin-punch can kill in a single ' + 'hit\nand earn you lifelong respect from your friends.' + ), 'Always remember to floss.', 'Don\'t run all the time. Really. You will fall off cliffs.', - ('In Capture-the-Flag, your own flag must be at your base to score, ' - 'If the other\nteam is about to score, stealing their flag can be ' - 'a good way to stop them.'), - ('If you get a sticky-bomb stuck to you, jump around and spin in ' - 'circles. You might\nshake the bomb off, or if nothing else your ' - 'last moments will be entertaining.'), - ('You take damage when you whack your head on things,\nso try to not ' - 'whack your head on things.'), + ( + 'In Capture-the-Flag, your own flag must be at your base to score, ' + 'If the other\nteam is about to score, stealing their flag can be ' + 'a good way to stop them.' + ), + ( + 'If you get a sticky-bomb stuck to you, jump around and spin in ' + 'circles. You might\nshake the bomb off, or if nothing else your ' + 'last moments will be entertaining.' + ), + ( + 'You take damage when you whack your head on things,\n' + 'so try to not whack your head on things.' + ), 'If you kill an enemy in one hit you get double points for it.', - ('Despite their looks, all characters\' abilities are identical,\nso ' - 'just pick whichever one you most closely resemble.'), + ( + 'Despite their looks, all characters\' abilities are identical,\n' + 'so just pick whichever one you most closely resemble.' + ), 'You can throw bombs higher if you jump just before throwing.', - ('Throw strength is based on the direction you are holding.\nTo toss ' - 'something gently in front of you, don\'t hold any direction.'), - ('If someone picks you up, punch them and they\'ll let go.\nThis ' - 'works in real life too.'), - ('Don\'t get too cocky with that energy shield; you can still get ' - 'yourself thrown off a cliff.'), - ('Many things can be picked up and thrown, including other players. ' - 'Tossing\nyour enemies off cliffs can be an effective and ' - 'emotionally fulfilling strategy.'), - ('Ice bombs are not very powerful, but they freeze\nwhoever they ' - 'hit, leaving them vulnerable to shattering.'), + ( + 'Throw strength is based on the direction you are holding.\n' + 'To toss something gently in front of you, don\'t' + ' hold any direction.' + ), + ( + 'If someone picks you up, punch them and they\'ll let go.\nThis ' + 'works in real life too.' + ), + ( + 'Don\'t get too cocky with that energy shield; you can still get ' + 'yourself thrown off a cliff.' + ), + ( + 'Many things can be picked up and thrown,' + ' including other players. ' + 'Tossing\nyour enemies off cliffs can be an effective and ' + 'emotionally fulfilling strategy.' + ), + ( + 'Ice bombs are not very powerful, but they freeze\nwhoever they ' + 'hit, leaving them vulnerable to shattering.' + ), 'Don\'t spin for too long; you\'ll become dizzy and fall.', - ('Run back and forth before throwing a bomb\nto \'whiplash\' it ' - 'and throw it farther.'), - ('Punches do more damage the faster your fists are moving,\nso ' - 'try running, jumping, and spinning like crazy.'), + ( + 'Run back and forth before throwing a bomb\nto \'whiplash\' it ' + 'and throw it farther.' + ), + ( + 'Punches do more damage the faster your fists are moving,\nso ' + 'try running, jumping, and spinning like crazy.' + ), 'In hockey, you\'ll maintain more speed if you turn gradually.', - ('The head is the most vulnerable area, so a sticky-bomb\nto the ' - 'noggin usually means game-over.'), - ('Hold down any button to run. You\'ll get places faster\nbut ' - 'won\'t turn very well, so watch out for cliffs.'), - ('You can judge when a bomb is going to explode based on the\n' - 'color of sparks from its fuse: yellow..orange..red..BOOM.'), + ( + 'The head is the most vulnerable area, so a sticky-bomb\nto the ' + 'noggin usually means game-over.' + ), + ( + 'Hold down any button to run. You\'ll get places faster\nbut ' + 'won\'t turn very well, so watch out for cliffs.' + ), + ( + 'You can judge when a bomb is going to explode based on the\n' + 'color of sparks from its fuse: yellow..orange..red..BOOM.' + ), ] app = _ba.app if not app.iircade_mode: @@ -81,12 +122,17 @@ def get_all_tips() -> list[str]: 'If your framerate is choppy, try turning down resolution\nor ' 'visuals in the game\'s graphics settings.' ] - if (app.platform in ('android', 'ios') and not app.on_tv - and not app.iircade_mode): + if ( + app.platform in ('android', 'ios') + and not app.on_tv + and not app.iircade_mode + ): tips += [ - ('If your device gets too warm or you\'d like to conserve ' - 'battery power,\nturn down "Visuals" or "Resolution" ' - 'in Settings->Graphics'), + ( + 'If your device gets too warm or you\'d like to conserve ' + 'battery power,\nturn down "Visuals" or "Resolution" ' + 'in Settings->Graphics' + ), ] if app.platform in ['mac', 'android'] and not app.iircade_mode: tips += [ diff --git a/dist/ba_data/python/ba/_tournament.py b/dist/ba_data/python/ba/_tournament.py index ec1618a..9694e38 100644 --- a/dist/ba_data/python/ba/_tournament.py +++ b/dist/ba_data/python/ba/_tournament.py @@ -17,6 +17,7 @@ def get_tournament_prize_strings(entry: dict[str, Any]) -> list[str]: # pylint: disable=too-many-locals from ba._generated.enums import SpecialChar from ba._gameutils import get_trophy_string + range1 = entry.get('prizeRange1') range2 = entry.get('prizeRange2') range3 = entry.get('prizeRange3') @@ -27,12 +28,18 @@ def get_tournament_prize_strings(entry: dict[str, Any]) -> list[str]: trophy_type_2 = entry.get('prizeTrophy2') trophy_type_3 = entry.get('prizeTrophy3') out_vals = [] - for rng, prize, trophy_type in ((range1, prize1, trophy_type_1), - (range2, prize2, trophy_type_2), - (range3, prize3, trophy_type_3)): - prval = ('' if rng is None else ('#' + str(rng[0])) if - (rng[0] == rng[1]) else - ('#' + str(rng[0]) + '-' + str(rng[1]))) + for rng, prize, trophy_type in ( + (range1, prize1, trophy_type_1), + (range2, prize2, trophy_type_2), + (range3, prize3, trophy_type_3), + ): + prval = ( + '' + if rng is None + else ('#' + str(rng[0])) + if (rng[0] == rng[1]) + else ('#' + str(rng[0]) + '-' + str(rng[1])) + ) pvval = '' if trophy_type is not None: pvval += get_trophy_string(trophy_type) @@ -40,8 +47,7 @@ def get_tournament_prize_strings(entry: dict[str, Any]) -> list[str]: # If we've got trophies but not for this entry, throw some space # in to compensate so the ticket counts line up. if prize is not None: - pvval = _ba.charstr( - SpecialChar.TICKET_BACKING) + str(prize) + pvval + pvval = _ba.charstr(SpecialChar.TICKET_BACKING) + str(prize) + pvval out_vals.append(prval) out_vals.append(pvval) return out_vals diff --git a/dist/ba_data/python/ba/_ui.py b/dist/ba_data/python/ba/_ui.py index 8ec2e27..c1aba30 100644 --- a/dist/ba_data/python/ba/_ui.py +++ b/dist/ba_data/python/ba/_ui.py @@ -89,19 +89,21 @@ class UISubsystem: if bool(False): # force-test ui scale self._uiscale = UIScale.SMALL with _ba.Context('ui'): - _ba.pushcall(lambda: _ba.screenmessage( - f'FORCING UISCALE {self._uiscale.name} FOR TESTING', - color=(1, 0, 1), - log=True)) + _ba.pushcall( + lambda: _ba.screenmessage( + f'FORCING UISCALE {self._uiscale.name} FOR TESTING', + color=(1, 0, 1), + log=True, + ) + ) self.controller = UIController() # Kick off our periodic UI upkeep. # FIXME: Can probably kill this if we do immediate UI death checks. - self.upkeeptimer = _ba.Timer(2.6543, - ui_upkeep, - timetype=TimeType.REAL, - repeat=True) + self.upkeeptimer = _ba.Timer( + 2.6543, ui_upkeep, timetype=TimeType.REAL, repeat=True + ) def set_main_menu_window(self, window: ba.Widget) -> None: """Set the current 'main' window, replacing any existing.""" @@ -122,6 +124,7 @@ class UISubsystem: frameline = f'{frameinfo.filename} {frameinfo.lineno}' except Exception: from ba._error import print_exception + print_exception('Error calcing line for set_main_menu_window') # With our legacy main-menu system, the caller is responsible for @@ -136,9 +139,12 @@ class UISubsystem: # things. def _delay_kill() -> None: import time + if existing: - print(f'Killing old main_menu_window' - f' when called at: {frameline} t={time.time():.3f}') + print( + f'Killing old main_menu_window' + f' when called at: {frameline} t={time.time():.3f}' + ) existing.delete() _ba.timer(1.0, _delay_kill, timetype=TimeType.REAL) @@ -148,8 +154,9 @@ class UISubsystem: """Clear any existing 'main' window with the provided transition.""" if self._main_menu_window: if transition is not None: - _ba.containerwidget(edit=self._main_menu_window, - transition=transition) + _ba.containerwidget( + edit=self._main_menu_window, transition=transition + ) else: self._main_menu_window.delete() diff --git a/dist/ba_data/python/ba/_workspace.py b/dist/ba_data/python/ba/_workspace.py index 16c9fca..c099739 100644 --- a/dist/ba_data/python/ba/_workspace.py +++ b/dist/ba_data/python/ba/_workspace.py @@ -36,6 +36,7 @@ class WorkspaceSubsystem: def set_active_workspace( self, + account: ba.AccountV2Handle, workspaceid: str, workspacename: str, on_completed: Callable[[], None], @@ -46,9 +47,11 @@ class WorkspaceSubsystem: # interactivity. Thread( target=lambda: self._set_active_workspace_bg( + account=account, workspaceid=workspaceid, workspacename=workspacename, - on_completed=on_completed), + on_completed=on_completed, + ), daemon=True, ).start() @@ -60,16 +63,22 @@ class WorkspaceSubsystem: _ba.screenmessage(msg, color=(0, 1, 0)) _ba.playsound(_ba.getsound('gunCocking')) - def _set_active_workspace_bg(self, workspaceid: str, workspacename: str, - on_completed: Callable[[], None]) -> None: + def _set_active_workspace_bg( + self, + account: ba.AccountV2Handle, + workspaceid: str, + workspacename: str, + on_completed: Callable[[], None], + ) -> None: from ba._language import Lstr class _SkipSyncError(RuntimeError): pass set_path = True - wspath = Path(_ba.get_volatile_data_directory(), 'workspaces', - workspaceid) + wspath = Path( + _ba.get_volatile_data_directory(), 'workspaces', workspaceid + ) try: # If it seems we're offline, don't even attempt a sync, @@ -85,15 +94,20 @@ class WorkspaceSubsystem: state = bacommon.cloud.WorkspaceFetchState(manifest=manifest) while True: - response = _ba.app.cloud.send_message( - bacommon.cloud.WorkspaceFetchMessage( - workspaceid=workspaceid, state=state)) + with account: + response = _ba.app.cloud.send_message( + bacommon.cloud.WorkspaceFetchMessage( + workspaceid=workspaceid, state=state + ) + ) state = response.state - self._handle_deletes(workspace_dir=wspath, - deletes=response.deletes) + self._handle_deletes( + workspace_dir=wspath, deletes=response.deletes + ) self._handle_downloads_inline( workspace_dir=wspath, - downloads_inline=response.downloads_inline) + downloads_inline=response.downloads_inline, + ) if response.done: # Server only deals in files; let's clean up any # leftover empty dirs after the dust has cleared. @@ -104,8 +118,10 @@ class WorkspaceSubsystem: _ba.pushcall( tpartial( self._successmsg, - Lstr(resource='activatedText', - subs=[('${THING}', workspacename)]), + Lstr( + resource='activatedText', + subs=[('${THING}', workspacename)], + ), ), from_other_thread=True, ) @@ -114,8 +130,11 @@ class WorkspaceSubsystem: _ba.pushcall( tpartial( self._errmsg, - Lstr(resource='workspaceSyncReuseText', - subs=[('${WORKSPACE}', workspacename)])), + Lstr( + resource='workspaceSyncReuseText', + subs=[('${WORKSPACE}', workspacename)], + ), + ), from_other_thread=True, ) @@ -123,8 +142,10 @@ class WorkspaceSubsystem: # Avoid reusing existing if we fail in the middle; could # be in wonky state. set_path = False - _ba.pushcall(tpartial(self._errmsg, Lstr(value=str(exc))), - from_other_thread=True) + _ba.pushcall( + tpartial(self._errmsg, Lstr(value=str(exc))), + from_other_thread=True, + ) except Exception: # Ditto. set_path = False @@ -132,8 +153,10 @@ class WorkspaceSubsystem: _ba.pushcall( tpartial( self._errmsg, - Lstr(resource='workspaceSyncErrorText', - subs=[('${WORKSPACE}', workspacename)]), + Lstr( + resource='workspaceSyncErrorText', + subs=[('${WORKSPACE}', workspacename)], + ), ), from_other_thread=True, ) @@ -186,8 +209,7 @@ class WorkspaceSubsystem: # listed when the parent dir is visited, so lets make sure # to only acknowledge still-existing ones. dirnames = [ - d for d in dirnames - if os.path.exists(os.path.join(basename, d)) + d for d in dirnames if os.path.exists(os.path.join(basename, d)) ] if not dirnames and not filenames and basename != prunedir: os.rmdir(basename) diff --git a/dist/ba_data/python/ba/internal.py b/dist/ba_data/python/ba/internal.py index fdf9980..cab4bab 100644 --- a/dist/ba_data/python/ba/internal.py +++ b/dist/ba_data/python/ba/internal.py @@ -9,73 +9,173 @@ defensively) in mods. from __future__ import annotations from _ba import ( - show_online_score_ui, set_ui_input_device, is_party_icon_visible, - getinputdevice, add_clean_frame_callback, unlock_all_input, - increment_analytics_count, set_debug_speed_exponent, get_special_widget, - get_qrcode_texture, get_string_height, get_string_width, show_app_invite, - appnameupper, lock_all_input, open_file_externally, fade_screen, appname, - have_incentivized_ad, has_video_ads, workspaces_in_use, - set_party_icon_always_visible, connect_to_party, get_game_port, - end_host_scanning, host_scan_cycle, charstr, get_public_party_enabled, - get_public_party_max_size, set_public_party_name, - set_public_party_max_size, set_authenticate_clients, - set_public_party_enabled, reset_random_player_names, new_host_session, - get_foreground_host_session, get_local_active_input_devices_count, - get_ui_input_device, is_in_replay, set_replay_speed_exponent, - get_replay_speed_exponent, disconnect_from_host, set_party_window_open, - get_connection_to_host_info, get_chat_messages, get_game_roster, - disconnect_client, chatmessage, get_random_names, have_permission, - request_permission, have_touchscreen_input, is_xcode_build, - set_low_level_config_value, get_low_level_config_value, - capture_gamepad_input, release_gamepad_input, has_gamma_control, - get_max_graphics_quality, get_display_resolution, capture_keyboard_input, - release_keyboard_input, value_test, set_touchscreen_editing, - is_running_on_fire_tv, android_get_external_files_dir, - set_telnet_access_enabled, new_replay_session, get_replays_dir) + show_online_score_ui, + set_ui_input_device, + is_party_icon_visible, + getinputdevice, + add_clean_frame_callback, + unlock_all_input, + increment_analytics_count, + set_debug_speed_exponent, + get_special_widget, + get_qrcode_texture, + get_string_height, + get_string_width, + show_app_invite, + appnameupper, + lock_all_input, + open_file_externally, + fade_screen, + appname, + have_incentivized_ad, + has_video_ads, + workspaces_in_use, + set_party_icon_always_visible, + connect_to_party, + get_game_port, + end_host_scanning, + host_scan_cycle, + charstr, + get_public_party_enabled, + get_public_party_max_size, + set_public_party_name, + set_public_party_max_size, + set_authenticate_clients, + set_public_party_enabled, + reset_random_player_names, + new_host_session, + get_foreground_host_session, + get_local_active_input_devices_count, + get_ui_input_device, + is_in_replay, + set_replay_speed_exponent, + get_replay_speed_exponent, + disconnect_from_host, + set_party_window_open, + get_connection_to_host_info, + get_chat_messages, + get_game_roster, + disconnect_client, + chatmessage, + get_random_names, + have_permission, + request_permission, + have_touchscreen_input, + is_xcode_build, + set_low_level_config_value, + get_low_level_config_value, + capture_gamepad_input, + release_gamepad_input, + has_gamma_control, + get_max_graphics_quality, + get_display_resolution, + capture_keyboard_input, + release_keyboard_input, + value_test, + set_touchscreen_editing, + is_running_on_fire_tv, + android_get_external_files_dir, + set_telnet_access_enabled, + new_replay_session, + get_replays_dir, +) -from ba._map import (get_map_class, register_map, preload_map_preview_media, - get_map_display_string, get_filtered_map_name) +from ba._map import ( + get_map_class, + register_map, + preload_map_preview_media, + get_map_display_string, + get_filtered_map_name, +) from ba._appconfig import commit_app_config -from ba._input import (get_device_value, get_input_map_hash, - get_input_device_config) +from ba._input import ( + get_device_value, + get_input_map_hash, + get_input_device_config, +) from ba._general import getclass, json_prep, get_type_name from ba._activitytypes import JoinActivity, ScoreScreenActivity -from ba._apputils import (is_browser_likely_available, get_remote_app_name, - should_submit_debug_info) -from ba._benchmark import (run_gpu_benchmark, run_cpu_benchmark, - run_media_reload_benchmark, run_stress_test) +from ba._apputils import ( + is_browser_likely_available, + get_remote_app_name, + should_submit_debug_info, +) +from ba._benchmark import ( + run_gpu_benchmark, + run_cpu_benchmark, + run_media_reload_benchmark, + run_stress_test, +) from ba._campaign import getcampaign from ba._messages import PlayerProfilesChangedMessage from ba._multiteamsession import DEFAULT_TEAM_COLORS, DEFAULT_TEAM_NAMES from ba._music import do_play_music -from ba._net import (master_server_get, master_server_post, - get_ip_address_type, DEFAULT_REQUEST_TIMEOUT_SECONDS) +from ba._net import ( + master_server_get, + master_server_post, + get_ip_address_type, + DEFAULT_REQUEST_TIMEOUT_SECONDS, +) from ba._powerup import get_default_powerup_distribution -from ba._profile import (get_player_profile_colors, get_player_profile_icon, - get_player_colors) +from ba._profile import ( + get_player_profile_colors, + get_player_profile_icon, + get_player_colors, +) from ba._tips import get_next_tip -from ba._playlist import (get_default_free_for_all_playlist, - get_default_teams_playlist, filter_playlist) -from ba._store import (get_available_sale_time, get_available_purchase_count, - get_store_item_name_translated, - get_store_item_display_size, get_store_layout, - get_store_item, get_clean_price, get_unowned_maps, - get_unowned_game_types) +from ba._playlist import ( + get_default_free_for_all_playlist, + get_default_teams_playlist, + filter_playlist, +) +from ba._store import ( + get_available_sale_time, + get_available_purchase_count, + get_store_item_name_translated, + get_store_item_display_size, + get_store_layout, + get_store_item, + get_clean_price, + get_unowned_maps, + get_unowned_game_types, +) from ba._tournament import get_tournament_prize_strings from ba._gameutils import get_trophy_string from ba._internal import ( - get_v2_fleet, get_master_server_address, is_blessed, get_news_show, - game_service_has_leaderboard, report_achievement, submit_score, - tournament_query, power_ranking_query, restore_purchases, purchase, - get_purchases_state, get_purchased, get_price, in_game_purchase, - add_transaction, reset_achievements, get_public_login_id, - have_outstanding_transactions, run_transactions, - get_v1_account_misc_read_val, get_v1_account_misc_read_val_2, - get_v1_account_misc_val, get_v1_account_ticket_count, - get_v1_account_state_num, get_v1_account_state, - get_v1_account_display_string, get_v1_account_type, get_v1_account_name, - sign_out_v1, sign_in_v1, mark_config_dirty) + get_v2_fleet, + get_master_server_address, + is_blessed, + get_news_show, + game_service_has_leaderboard, + report_achievement, + submit_score, + tournament_query, + power_ranking_query, + restore_purchases, + purchase, + get_purchases_state, + get_purchased, + get_price, + in_game_purchase, + add_transaction, + reset_achievements, + get_public_login_id, + have_outstanding_transactions, + run_transactions, + get_v1_account_misc_read_val, + get_v1_account_misc_read_val_2, + get_v1_account_misc_val, + get_v1_account_ticket_count, + get_v1_account_state_num, + get_v1_account_state, + get_v1_account_display_string, + get_v1_account_type, + get_v1_account_name, + sign_out_v1, + sign_in_v1, + mark_config_dirty, +) __all__ = [ 'show_online_score_ui', diff --git a/dist/ba_data/python/ba/macmusicapp.py b/dist/ba_data/python/ba/macmusicapp.py index 4a75872..38f4e15 100644 --- a/dist/ba_data/python/ba/macmusicapp.py +++ b/dist/ba_data/python/ba/macmusicapp.py @@ -24,12 +24,18 @@ class MacMusicAppMusicPlayer(MusicPlayer): self._thread = _MacMusicAppThread() self._thread.start() - def on_select_entry(self, callback: Callable[[Any], None], - current_entry: Any, selection_target_name: str) -> Any: + def on_select_entry( + self, + callback: Callable[[Any], None], + current_entry: Any, + selection_target_name: str, + ) -> Any: # pylint: disable=cyclic-import from bastd.ui.soundtrack import entrytypeselect as etsel - return etsel.SoundtrackEntryTypeSelectWindow(callback, current_entry, - selection_target_name) + + return etsel.SoundtrackEntryTypeSelectWindow( + callback, current_entry, selection_target_name + ) def on_set_volume(self, volume: float) -> None: self._thread.set_volume(volume) @@ -44,8 +50,10 @@ class MacMusicAppMusicPlayer(MusicPlayer): if entry_type == 'iTunesPlaylist': self._thread.play_playlist(music.get_soundtrack_entry_name(entry)) else: - print('MacMusicAppMusicPlayer passed unrecognized entry type:', - entry_type) + print( + 'MacMusicAppMusicPlayer passed unrecognized entry type:', + entry_type, + ) def on_stop(self) -> None: self._thread.play_playlist(None) @@ -70,6 +78,7 @@ class _MacMusicAppThread(threading.Thread): from ba._general import Call from ba._language import Lstr from ba._generated.enums import TimeType + _ba.set_thread_name('BA_MacMusicAppThread') _ba.mac_music_app_init() @@ -77,10 +86,15 @@ class _MacMusicAppThread(threading.Thread): # it causes any funny business (this used to background the app # sometimes, though I think that is fixed now) def do_print() -> None: - _ba.timer(1.0, - Call(_ba.screenmessage, Lstr(resource='usingItunesText'), - (0, 1, 0)), - timetype=TimeType.REAL) + _ba.timer( + 1.0, + Call( + _ba.screenmessage, + Lstr(resource='usingItunesText'), + (0, 1, 0), + ), + timetype=TimeType.REAL, + ) _ba.pushcall(do_print, from_other_thread=True) @@ -153,15 +167,29 @@ class _MacMusicAppThread(threading.Thread): self._commands_available.set() def _handle_get_playlists_command( - self, target: Callable[[list[str]], None]) -> None: + self, target: Callable[[list[str]], None] + ) -> None: from ba._general import Call + try: playlists = _ba.mac_music_app_get_playlists() playlists = [ - p for p in playlists if p not in [ - 'Music', 'Movies', 'TV Shows', 'Podcasts', 'iTunes\xa0U', - 'Books', 'Genius', 'iTunes DJ', 'Music Videos', - 'Home Videos', 'Voice Memos', 'Audiobooks' + p + for p in playlists + if p + not in [ + 'Music', + 'Movies', + 'TV Shows', + 'Podcasts', + 'iTunes\xa0U', + 'Books', + 'Genius', + 'iTunes DJ', + 'Music Videos', + 'Home Videos', + 'Voice Memos', + 'Audiobooks', ] ] playlists.sort(key=lambda x: x.lower()) @@ -194,7 +222,7 @@ class _MacMusicAppThread(threading.Thread): # Set our playlist and play it if our volume is up. self._current_playlist = target if self._volume > 0: - self._orig_volume = (_ba.mac_music_app_get_volume()) + self._orig_volume = _ba.mac_music_app_get_volume() self._update_mac_music_app_volume() self._play_current_playlist() @@ -213,20 +241,30 @@ class _MacMusicAppThread(threading.Thread): def _play_current_playlist(self) -> None: try: from ba._general import Call + assert self._current_playlist is not None if _ba.mac_music_app_play_playlist(self._current_playlist): pass else: - _ba.pushcall(Call( - _ba.screenmessage, - _ba.app.lang.get_resource('playlistNotFoundText') + - ': \'' + self._current_playlist + '\'', (1, 0, 0)), - from_other_thread=True) + _ba.pushcall( + Call( + _ba.screenmessage, + _ba.app.lang.get_resource('playlistNotFoundText') + + ': \'' + + self._current_playlist + + '\'', + (1, 0, 0), + ), + from_other_thread=True, + ) except Exception: from ba import _error + _error.print_exception( - f'error playing playlist {self._current_playlist}') + f'error playing playlist {self._current_playlist}' + ) def _update_mac_music_app_volume(self) -> None: _ba.mac_music_app_set_volume( - max(0, min(100, int(100.0 * self._volume)))) + max(0, min(100, int(100.0 * self._volume))) + ) diff --git a/dist/ba_data/python/ba/modutils.py b/dist/ba_data/python/ba/modutils.py index c081d79..2bda5c4 100644 --- a/dist/ba_data/python/ba/modutils.py +++ b/dist/ba_data/python/ba/modutils.py @@ -47,10 +47,12 @@ def _request_storage_permission() -> bool: """If needed, requests storage permission from the user (& return true).""" from ba._language import Lstr from ba._generated.enums import Permission + if not _ba.have_permission(Permission.STORAGE): _ba.playsound(_ba.getsound('error')) - _ba.screenmessage(Lstr(resource='storagePermissionAccessText'), - color=(1, 0, 0)) + _ba.screenmessage( + Lstr(resource='storagePermissionAccessText'), color=(1, 0, 0) + ) _ba.timer(1.0, lambda: _ba.request_permission(Permission.STORAGE)) return True return False @@ -80,12 +82,15 @@ def show_user_scripts() -> None: if usd is not None and os.path.isdir(usd): file_name = usd + '/about_this_folder.txt' with open(file_name, 'w', encoding='utf-8') as outfile: - outfile.write('You can drop files in here to mod the game.' - ' See settings/advanced' - ' in the game for more info.') + outfile.write( + 'You can drop files in here to mod the game.' + ' See settings/advanced' + ' in the game for more info.' + ) _ba.android_media_scan_file(file_name) except Exception: from ba import _error + _error.print_exception('error writing about_this_folder stuff') # On a few platforms we try to open the dir in the UI. @@ -103,13 +108,14 @@ def create_user_system_scripts() -> None: (for editing and experiment with) """ import shutil + app = _ba.app # First off, if we need permission for this, ask for it. if _request_storage_permission(): return - path = (app.python_directory_user + '/sys/' + app.version) + path = app.python_directory_user + '/sys/' + app.version pathtmp = path + '_tmp' if os.path.exists(path): shutil.rmtree(path) @@ -123,31 +129,38 @@ def create_user_system_scripts() -> None: # to blow them away anyway to make changes; # See https://github.com/efroemling/ballistica/wiki # /Knowledge-Nuggets#python-cache-files-gotcha - return ('__pycache__', ) + return ('__pycache__',) print(f'COPYING "{app.python_directory_app}" -> "{pathtmp}".') shutil.copytree(app.python_directory_app, pathtmp, ignore=_ignore_filter) print(f'MOVING "{pathtmp}" -> "{path}".') shutil.move(pathtmp, path) - print(f"Created system scripts at :'{path}" - f"'\nRestart {_ba.appname()} to use them." - f' (use ba.quit() to exit the game)') + print( + f"Created system scripts at :'{path}" + f"'\nRestart {_ba.appname()} to use them." + f' (use ba.quit() to exit the game)' + ) if app.platform == 'android': - print('Note: the new files may not be visible via ' - 'android-file-transfer until you restart your device.') + print( + 'Note: the new files may not be visible via ' + 'android-file-transfer until you restart your device.' + ) def delete_user_system_scripts() -> None: """Clean out the scripts created by create_user_system_scripts().""" import shutil + app = _ba.app - path = (app.python_directory_user + '/sys/' + app.version) + path = app.python_directory_user + '/sys/' + app.version if os.path.exists(path): shutil.rmtree(path) - print(f'User system scripts deleted.\n' - f'Restart {_ba.appname()} to use internal' - f' scripts. (use ba.quit() to exit the game)') + print( + f'User system scripts deleted.\n' + f'Restart {_ba.appname()} to use internal' + f' scripts. (use ba.quit() to exit the game)' + ) else: print('User system scripts not found.') diff --git a/dist/ba_data/python/ba/osmusic.py b/dist/ba_data/python/ba/osmusic.py index 624e681..9c3f48e 100644 --- a/dist/ba_data/python/ba/osmusic.py +++ b/dist/ba_data/python/ba/osmusic.py @@ -31,13 +31,20 @@ class OSMusicPlayer(MusicPlayer): # FIXME: should ask the C++ layer for these; just hard-coding for now. return ['mp3', 'ogg', 'm4a', 'wav', 'flac', 'mid'] - def on_select_entry(self, callback: Callable[[Any], None], - current_entry: Any, selection_target_name: str) -> Any: + def on_select_entry( + self, + callback: Callable[[Any], None], + current_entry: Any, + selection_target_name: str, + ) -> Any: # pylint: disable=cyclic-import from bastd.ui.soundtrack.entrytypeselect import ( - SoundtrackEntryTypeSelectWindow) - return SoundtrackEntryTypeSelectWindow(callback, current_entry, - selection_target_name) + SoundtrackEntryTypeSelectWindow, + ) + + return SoundtrackEntryTypeSelectWindow( + callback, current_entry, selection_target_name + ) def on_set_volume(self, volume: float) -> None: _ba.music_player_set_volume(volume) @@ -56,22 +63,31 @@ class OSMusicPlayer(MusicPlayer): # valid file within it. self._want_to_play = True self._actually_playing = False - _PickFolderSongThread(name, self.get_valid_music_file_extensions(), - self._on_play_folder_cb).start() + _PickFolderSongThread( + name, + self.get_valid_music_file_extensions(), + self._on_play_folder_cb, + ).start() - def _on_play_folder_cb(self, - result: str | list[str], - error: str | None = None) -> None: + def _on_play_folder_cb( + self, result: str | list[str], error: str | None = None + ) -> None: from ba import _language + if error is not None: - rstr = (_language.Lstr( - resource='internal.errorPlayingMusicText').evaluate()) + rstr = _language.Lstr( + resource='internal.errorPlayingMusicText' + ).evaluate() if isinstance(result, str): - err_str = (rstr.replace('${MUSIC}', os.path.basename(result)) + - '; ' + str(error)) + err_str = ( + rstr.replace('${MUSIC}', os.path.basename(result)) + + '; ' + + str(error) + ) else: - err_str = (rstr.replace('${MUSIC}', '') + '; ' + - str(error)) + err_str = ( + rstr.replace('${MUSIC}', '') + '; ' + str(error) + ) _ba.screenmessage(err_str, color=(1, 0, 0)) return @@ -93,9 +109,12 @@ class OSMusicPlayer(MusicPlayer): class _PickFolderSongThread(threading.Thread): - - def __init__(self, path: str, valid_extensions: list[str], - callback: Callable[[str | list[str], str | None], None]): + def __init__( + self, + path: str, + valid_extensions: list[str], + callback: Callable[[str | list[str], str | None], None], + ): super().__init__() self._valid_extensions = valid_extensions self._callback = callback @@ -104,6 +123,7 @@ class _PickFolderSongThread(threading.Thread): def run(self) -> None: from ba import _language from ba._general import Call + do_print_error = True try: _ba.set_thread_name('BA_PickFolderSongThread') @@ -111,24 +131,33 @@ class _PickFolderSongThread(threading.Thread): valid_extensions = ['.' + x for x in self._valid_extensions] for root, _subdirs, filenames in os.walk(self._path): for fname in filenames: - if any(fname.lower().endswith(ext) - for ext in valid_extensions): - all_files.insert(random.randrange(len(all_files) + 1), - root + '/' + fname) + if any( + fname.lower().endswith(ext) for ext in valid_extensions + ): + all_files.insert( + random.randrange(len(all_files) + 1), + root + '/' + fname, + ) if not all_files: do_print_error = False raise RuntimeError( - _language.Lstr(resource='internal.noMusicFilesInFolderText' - ).evaluate()) - _ba.pushcall(Call(self._callback, all_files, None), - from_other_thread=True) + _language.Lstr( + resource='internal.noMusicFilesInFolderText' + ).evaluate() + ) + _ba.pushcall( + Call(self._callback, all_files, None), from_other_thread=True + ) except Exception as exc: from ba import _error + if do_print_error: _error.print_exception() try: err_str = str(exc) except Exception: err_str = '' - _ba.pushcall(Call(self._callback, self._path, err_str), - from_other_thread=True) + _ba.pushcall( + Call(self._callback, self._path, err_str), + from_other_thread=True, + ) diff --git a/dist/ba_data/python/ba/ui/__init__.py b/dist/ba_data/python/ba/ui/__init__.py index 67e5d93..3d9740b 100644 --- a/dist/ba_data/python/ba/ui/__init__.py +++ b/dist/ba_data/python/ba/ui/__init__.py @@ -11,7 +11,6 @@ from typing import TYPE_CHECKING, cast, Type import _ba from ba._generated.enums import TimeType -from ba._general import print_active_refs if TYPE_CHECKING: from typing import Any @@ -44,6 +43,7 @@ class Window: @dataclass class UICleanupCheck: """Holds info about a uicleanupcheck target.""" + obj: weakref.ref widget: ba.Widget widget_death_time: float | None @@ -112,6 +112,7 @@ class UIEntry: # TEMP HARD CODED - WILL REPLACE THIS WITH BA_META LOOKUPS. if self._name == 'mainmenu': from bastd.ui import mainmenu + return cast(Type[UILocation], mainmenu.MainMenuWindow) raise ValueError('unknown ui class ' + str(self._name)) @@ -139,8 +140,9 @@ class UIController: """Show the main menu, clearing other UIs from location stacks.""" self._main_stack = [] self._dialog_stack = [] - self._main_stack = (self._main_stack_game - if in_game else self._main_stack_menu) + self._main_stack = ( + self._main_stack_game if in_game else self._main_stack_menu + ) self._main_stack.append(UIEntry('mainmenu', self)) self._update_ui() @@ -154,8 +156,13 @@ class UIController: entry.destroy() # Now create the topmost one if there is one. - entrynew = (self._dialog_stack[-1] if self._dialog_stack else - self._main_stack[-1] if self._main_stack else None) + entrynew = ( + self._dialog_stack[-1] + if self._dialog_stack + else self._main_stack[-1] + if self._main_stack + else None + ) if entrynew is not None: entrynew.create() @@ -190,9 +197,10 @@ def uicleanupcheck(obj: Any, widget: ba.Widget) -> None: widget.add_delete_callback(foobar) _ba.app.ui.cleanupchecks.append( - UICleanupCheck(obj=weakref.ref(obj), - widget=widget, - widget_death_time=None)) + UICleanupCheck( + obj=weakref.ref(obj), widget=widget, widget_death_time=None + ) + ) def ui_upkeep() -> None: @@ -218,11 +226,12 @@ def ui_upkeep() -> None: # Widget was already dead; complain if its been too long. if now - check.widget_death_time > 5.0: print( - 'WARNING:', obj, + 'WARNING:', + obj, 'is still alive 5 second after its widget died;' - ' you might have a memory leak.') - print_active_refs(obj) - + ' you might have a memory leak. See efro.debug module' + ' for tools to help debug this.', + ) else: remainingchecks.append(check) ui.cleanupchecks = remainingchecks diff --git a/dist/ba_data/python/bacommon/assets.py b/dist/ba_data/python/bacommon/assets.py index 2c4441a..159307d 100644 --- a/dist/ba_data/python/bacommon/assets.py +++ b/dist/ba_data/python/bacommon/assets.py @@ -26,6 +26,7 @@ class AssetPackageFlavor(Enum): class AssetType(Enum): """Types for individual assets within a package.""" + TEXTURE = 'texture' CUBE_TEXTURE = 'cube_texture' SOUND = 'sound' @@ -38,8 +39,10 @@ class AssetType(Enum): @dataclass class AssetPackageFlavorManifest: """A manifest of asset info for a specific flavor of an asset package.""" - cloudfiles: Annotated[dict[str, str], - IOAttrs('cloudfiles')] = field(default_factory=dict) + + cloudfiles: Annotated[dict[str, str], IOAttrs('cloudfiles')] = field( + default_factory=dict + ) @ioprepped @@ -48,8 +51,9 @@ class AssetPackageBuildState: """Contains info about an in-progress asset cloud build.""" # Asset names still being built. - in_progress_builds: Annotated[list[str], - IOAttrs('b')] = field(default_factory=list) + in_progress_builds: Annotated[list[str], IOAttrs('b')] = field( + default_factory=list + ) # The initial number of assets needing to be built. initial_build_count: Annotated[int, IOAttrs('c')] = 0 diff --git a/dist/ba_data/python/bacommon/bacloud.py b/dist/ba_data/python/bacommon/bacloud.py index 2453aa8..2f41f62 100644 --- a/dist/ba_data/python/bacommon/bacloud.py +++ b/dist/ba_data/python/bacommon/bacloud.py @@ -21,6 +21,7 @@ BACLOUD_VERSION = 8 @dataclass class RequestData: """Request sent to bacloud server.""" + command: Annotated[str, IOAttrs('c')] token: Annotated[str | None, IOAttrs('t')] payload: Annotated[dict, IOAttrs('p')] @@ -74,23 +75,32 @@ class ResponseData: delay_seconds: Annotated[float, IOAttrs('d', store_default=False)] = 0.0 login: Annotated[str | None, IOAttrs('l', store_default=False)] = None logout: Annotated[bool, IOAttrs('lo', store_default=False)] = False - dir_manifest: Annotated[str | None, - IOAttrs('man', store_default=False)] = None - uploads: Annotated[tuple[list[str], str, dict] | None, - IOAttrs('u', store_default=False)] = None - uploads_inline: Annotated[list[str] | None, - IOAttrs('uinl', store_default=False)] = None - deletes: Annotated[list[str] | None, - IOAttrs('dlt', store_default=False)] = None - downloads_inline: Annotated[dict[str, str] | None, - IOAttrs('dinl', store_default=False)] = None - dir_prune_empty: Annotated[str | None, - IOAttrs('dpe', store_default=False)] = None + dir_manifest: Annotated[ + str | None, IOAttrs('man', store_default=False) + ] = None + uploads: Annotated[ + tuple[list[str], str, dict] | None, IOAttrs('u', store_default=False) + ] = None + uploads_inline: Annotated[ + list[str] | None, IOAttrs('uinl', store_default=False) + ] = None + deletes: Annotated[ + list[str] | None, IOAttrs('dlt', store_default=False) + ] = None + downloads_inline: Annotated[ + dict[str, str] | None, IOAttrs('dinl', store_default=False) + ] = None + dir_prune_empty: Annotated[ + str | None, IOAttrs('dpe', store_default=False) + ] = None open_url: Annotated[str | None, IOAttrs('url', store_default=False)] = None - input_prompt: Annotated[tuple[str, bool] | None, - IOAttrs('inp', store_default=False)] = None - end_message: Annotated[str | None, - IOAttrs('em', store_default=False)] = None + input_prompt: Annotated[ + tuple[str, bool] | None, IOAttrs('inp', store_default=False) + ] = None + end_message: Annotated[ + str | None, IOAttrs('em', store_default=False) + ] = None end_message_end: Annotated[str, IOAttrs('eme', store_default=False)] = '\n' - end_command: Annotated[tuple[str, dict] | None, - IOAttrs('ec', store_default=False)] = None + end_command: Annotated[ + tuple[str, dict] | None, IOAttrs('ec', store_default=False) + ] = None diff --git a/dist/ba_data/python/bacommon/build.py b/dist/ba_data/python/bacommon/build.py index 44ce489..0ffcd3f 100644 --- a/dist/ba_data/python/bacommon/build.py +++ b/dist/ba_data/python/bacommon/build.py @@ -22,6 +22,7 @@ class BuildInfoSet: @dataclass class Entry: """Info about a particular build.""" + filename: Annotated[str, IOAttrs('fname')] size: Annotated[int, IOAttrs('size')] version: Annotated[str, IOAttrs('version')] @@ -29,5 +30,6 @@ class BuildInfoSet: checksum: Annotated[str, IOAttrs('checksum')] createtime: Annotated[datetime.datetime, IOAttrs('createtime')] - builds: Annotated[list[Entry], - IOAttrs('builds')] = field(default_factory=list) + builds: Annotated[list[Entry], IOAttrs('builds')] = field( + default_factory=list + ) diff --git a/dist/ba_data/python/bacommon/cloud.py b/dist/ba_data/python/bacommon/cloud.py index 91512f9..431dac5 100644 --- a/dist/ba_data/python/bacommon/cloud.py +++ b/dist/ba_data/python/bacommon/cloud.py @@ -44,6 +44,7 @@ class LoginProxyRequestResponse(Response): @dataclass class LoginProxyStateQueryMessage(Message): """Soo.. how is that login proxy going?""" + proxyid: Annotated[str, IOAttrs('p')] proxykey: Annotated[str, IOAttrs('k')] @@ -59,6 +60,7 @@ class LoginProxyStateQueryResponse(Response): class State(Enum): """States a login-proxy can be in.""" + WAITING = 'waiting' SUCCESS = 'success' FAIL = 'fail' @@ -73,6 +75,7 @@ class LoginProxyStateQueryResponse(Response): @dataclass class LoginProxyCompleteMessage(Message): """Just so you know, we're done with this proxy.""" + proxyid: Annotated[str, IOAttrs('p')] @@ -96,6 +99,7 @@ class PingResponse(Response): @dataclass class TestMessage(Message): """Can I get some of that workspace action?""" + testfoo: Annotated[int, IOAttrs('f')] @classmethod @@ -115,6 +119,7 @@ class TestResponse(Response): @dataclass class WorkspaceFetchState: """Common state data for a workspace fetch.""" + manifest: Annotated[DirectoryManifest, IOAttrs('m')] iteration: Annotated[int, IOAttrs('i')] = 0 total_deletes: Annotated[int, IOAttrs('tdels')] = 0 @@ -126,6 +131,7 @@ class WorkspaceFetchState: @dataclass class WorkspaceFetchMessage(Message): """Can I get some of that workspace action?""" + workspaceid: Annotated[str, IOAttrs('w')] state: Annotated[WorkspaceFetchState, IOAttrs('s')] @@ -140,11 +146,11 @@ class WorkspaceFetchResponse(Response): """Here's that workspace you asked for, boss.""" state: Annotated[WorkspaceFetchState, IOAttrs('s')] - deletes: Annotated[list[str], - IOAttrs('dlt', store_default=False)] = field( - default_factory=list) - downloads_inline: Annotated[dict[str, bytes], - IOAttrs('dinl', store_default=False)] = field( - default_factory=dict) + deletes: Annotated[list[str], IOAttrs('dlt', store_default=False)] = field( + default_factory=list + ) + downloads_inline: Annotated[ + dict[str, bytes], IOAttrs('dinl', store_default=False) + ] = field(default_factory=dict) done: Annotated[bool, IOAttrs('d')] = False diff --git a/dist/ba_data/python/bacommon/net.py b/dist/ba_data/python/bacommon/net.py index a22a9c5..2cc97df 100644 --- a/dist/ba_data/python/bacommon/net.py +++ b/dist/ba_data/python/bacommon/net.py @@ -18,6 +18,7 @@ if TYPE_CHECKING: @dataclass class ServerNodeEntry: """Information about a specific server.""" + zone: Annotated[str, IOAttrs('r')] address: Annotated[str, IOAttrs('a')] port: Annotated[int, IOAttrs('p')] @@ -35,15 +36,16 @@ class ServerNodeQueryResponse: error: Annotated[str | None, IOAttrs('e', store_default=False)] = None # The set of servernodes. - servers: Annotated[list[ServerNodeEntry], - IOAttrs('s', store_default=False)] = field( - default_factory=list) + servers: Annotated[ + list[ServerNodeEntry], IOAttrs('s', store_default=False) + ] = field(default_factory=list) @ioprepped @dataclass class PrivateHostingState: """Combined state of whether we're hosting, whether we can, etc.""" + unavailable_error: str | None = None party_code: str | None = None tickets_to_host_now: int = 0 @@ -55,13 +57,15 @@ class PrivateHostingState: @dataclass class PrivateHostingConfig: """Config provided when hosting a private party.""" + session_type: str = 'ffa' playlist_name: str = 'Unknown' randomize: bool = False tutorial: bool = False custom_team_names: tuple[str, str] | None = None - custom_team_colors: tuple[tuple[float, float, float], - tuple[float, float, float]] | None = None + custom_team_colors: tuple[ + tuple[float, float, float], tuple[float, float, float] + ] | None = None playlist: list[dict[str, Any]] | None = None exit_minutes: float = 120.0 exit_minutes_unclean: float = 180.0 @@ -72,6 +76,7 @@ class PrivateHostingConfig: @dataclass class PrivatePartyConnectResult: """Info about a server we get back when connecting.""" + error: str | None = None addr: str | None = None port: int | None = None diff --git a/dist/ba_data/python/bacommon/servermanager.py b/dist/ba_data/python/bacommon/servermanager.py index 8dd5b1d..e21b1d1 100644 --- a/dist/ba_data/python/bacommon/servermanager.py +++ b/dist/ba_data/python/bacommon/servermanager.py @@ -134,8 +134,9 @@ class ServerConfig: team_names: tuple[str, str] | None = None # Team colors (teams mode only). - team_colors: tuple[tuple[float, float, float], tuple[float, float, - float]] | None = None + team_colors: tuple[ + tuple[float, float, float], tuple[float, float, float] + ] | None = None # (internal) stress-testing mode. stress_test_players: int | None = None @@ -151,11 +152,13 @@ class ServerCommand: @dataclass class StartServerModeCommand(ServerCommand): """Tells the app to switch into 'server' mode.""" + config: ServerConfig class ShutdownReason(Enum): """Reason a server is shutting down.""" + NONE = 'none' RESTARTING = 'restarting' @@ -163,6 +166,7 @@ class ShutdownReason(Enum): @dataclass class ShutdownCommand(ServerCommand): """Tells the server to shut down.""" + reason: ShutdownReason immediate: bool @@ -170,6 +174,7 @@ class ShutdownCommand(ServerCommand): @dataclass class ChatMessageCommand(ServerCommand): """Chat message from the server.""" + message: str clients: list[int] | None @@ -177,6 +182,7 @@ class ChatMessageCommand(ServerCommand): @dataclass class ScreenMessageCommand(ServerCommand): """Screen-message from the server.""" + message: str color: tuple[float, float, float] | None clients: list[int] | None @@ -190,5 +196,6 @@ class ClientListCommand(ServerCommand): @dataclass class KickCommand(ServerCommand): """Kick a client.""" + client_id: int ban_time: int | None diff --git a/dist/ba_data/python/bacommon/transfer.py b/dist/ba_data/python/bacommon/transfer.py index 6504287..c0b4492 100644 --- a/dist/ba_data/python/bacommon/transfer.py +++ b/dist/ba_data/python/bacommon/transfer.py @@ -19,6 +19,7 @@ if TYPE_CHECKING: @dataclass class DirectoryManifestFile: """Describes metadata and hashes for a file in a manifest.""" + filehash: Annotated[str, IOAttrs('h')] filesize: Annotated[int, IOAttrs('s')] @@ -27,6 +28,7 @@ class DirectoryManifestFile: @dataclass class DirectoryManifest: """Contains a summary of files in a directory.""" + files: Annotated[dict[str, DirectoryManifestFile], IOAttrs('f')] _empty_hash: str | None = None @@ -48,7 +50,7 @@ class DirectoryManifest: assert fullname.startswith(pathstr) # Make sure we end up with forward slashes no matter # what the os.* stuff above here was using. - paths.append(Path(fullname[len(pathstr) + 1:]).as_posix()) + paths.append(Path(fullname[len(pathstr) + 1 :]).as_posix()) elif path.exists(): # Just return a single file entry if path is not a dir. paths.append(path.as_posix()) @@ -62,9 +64,12 @@ class DirectoryManifest: filebytes = infile.read() filesize = len(filebytes) sha.update(filebytes) - return (filepath, - DirectoryManifestFile(filehash=sha.hexdigest(), - filesize=filesize)) + return ( + filepath, + DirectoryManifestFile( + filehash=sha.hexdigest(), filesize=filesize + ), + ) # Now use all procs to hash the files efficiently. cpus = os.cpu_count() @@ -76,13 +81,15 @@ class DirectoryManifest: def validate(self) -> None: """Log any odd data in the manifest; for debugging.""" import logging + for fpath, _fentry in self.files.items(): # We want to be dealing in only forward slashes; make sure # that's the case (wondering if we'll ever see backslashes # for escape purposes). if '\\' in fpath: - logging.exception("Found unusual path in manifest: '%s'.", - fpath) + logging.exception( + "Found unusual path in manifest: '%s'.", fpath + ) break # 1 error is enough for now. @classmethod @@ -90,6 +97,7 @@ class DirectoryManifest: """Return the hash for an empty file.""" if cls._empty_hash is None: import hashlib + sha = hashlib.sha256() cls._empty_hash = sha.hexdigest() return cls._empty_hash diff --git a/dist/ba_data/python/bastd/activity/coopjoin.py b/dist/ba_data/python/bastd/activity/coopjoin.py index b8cc8af..ebe9dea 100644 --- a/dist/ba_data/python/bastd/activity/coopjoin.py +++ b/dist/ba_data/python/bastd/activity/coopjoin.py @@ -27,19 +27,23 @@ class CoopJoinActivity(JoinActivity): def on_transition_in(self) -> None: from bastd.actor.controlsguide import ControlsGuide from bastd.actor.text import Text + super().on_transition_in() assert isinstance(self.session, ba.CoopSession) assert self.session.campaign - Text(self.session.campaign.getlevel( - self.session.campaign_level_name).displayname, - scale=1.3, - h_attach=Text.HAttach.CENTER, - h_align=Text.HAlign.CENTER, - v_attach=Text.VAttach.TOP, - transition=Text.Transition.FADE_IN, - transition_delay=4.0, - color=(1, 1, 1, 0.6), - position=(0, -95)).autoretain() + Text( + self.session.campaign.getlevel( + self.session.campaign_level_name + ).displayname, + scale=1.3, + h_attach=Text.HAttach.CENTER, + h_align=Text.HAlign.CENTER, + v_attach=Text.VAttach.TOP, + transition=Text.Transition.FADE_IN, + transition_delay=4.0, + color=(1, 1, 1, 0.6), + position=(0, -95), + ).autoretain() ControlsGuide(delay=1.0).autoretain() ba.pushcall(self._show_remaining_achievements) @@ -60,30 +64,34 @@ class CoopJoinActivity(JoinActivity): # Now list our remaining achievements for this level. assert self.session.campaign is not None assert isinstance(self.session, ba.CoopSession) - levelname = (self.session.campaign.name + ':' + - self.session.campaign_level_name) + levelname = ( + self.session.campaign.name + ':' + self.session.campaign_level_name + ) ts_h_offs = 60 if not (ba.app.demo_mode or ba.app.arcade_mode): achievements = [ - a for a in ba.app.ach.achievements_for_coop_level(levelname) + a + for a in ba.app.ach.achievements_for_coop_level(levelname) if not a.complete ] have_achievements = bool(achievements) achievements = [a for a in achievements if not a.complete] vrmode = ba.app.vr_mode if have_achievements: - Text(ba.Lstr(resource='achievementsRemainingText'), - host_only=True, - position=(ts_h_offs - 10, vpos), - transition=Text.Transition.FADE_IN, - scale=1.1 * 0.76, - h_attach=Text.HAttach.LEFT, - v_attach=Text.VAttach.TOP, - color=(1, 1, 1.2, 1) if vrmode else (0.8, 0.8, 1, 1), - shadow=1.0, - flatness=1.0 if vrmode else 0.6, - transition_delay=delay).autoretain() + Text( + ba.Lstr(resource='achievementsRemainingText'), + host_only=True, + position=(ts_h_offs - 10, vpos), + transition=Text.Transition.FADE_IN, + scale=1.1 * 0.76, + h_attach=Text.HAttach.LEFT, + v_attach=Text.VAttach.TOP, + color=(1, 1, 1.2, 1) if vrmode else (0.8, 0.8, 1, 1), + shadow=1.0, + flatness=1.0 if vrmode else 0.6, + transition_delay=delay, + ).autoretain() hval = ts_h_offs + 50 vpos -= 35 for ach in achievements: @@ -91,12 +99,14 @@ class CoopJoinActivity(JoinActivity): ach.create_display(hval, vpos, delay, style='in_game') vpos -= 55 if not achievements: - Text(ba.Lstr(resource='noAchievementsRemainingText'), - host_only=True, - position=(ts_h_offs + 15, vpos + 10), - transition=Text.Transition.FADE_IN, - scale=0.7, - h_attach=Text.HAttach.LEFT, - v_attach=Text.VAttach.TOP, - color=(1, 1, 1, 0.5), - transition_delay=delay + 0.5).autoretain() + Text( + ba.Lstr(resource='noAchievementsRemainingText'), + host_only=True, + position=(ts_h_offs + 15, vpos + 10), + transition=Text.Transition.FADE_IN, + scale=0.7, + h_attach=Text.HAttach.LEFT, + v_attach=Text.VAttach.TOP, + color=(1, 1, 1, 0.5), + transition_delay=delay + 0.5, + ).autoretain() diff --git a/dist/ba_data/python/bastd/activity/coopscore.py b/dist/ba_data/python/bastd/activity/coopscore.py index 5e71b05..5c32fde 100644 --- a/dist/ba_data/python/bastd/activity/coopscore.py +++ b/dist/ba_data/python/bastd/activity/coopscore.py @@ -49,12 +49,16 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): self._campaign: ba.Campaign = settings['campaign'] self._have_achievements = bool( - ba.app.ach.achievements_for_coop_level(self._campaign.name + ':' + - settings['level'])) + ba.app.ach.achievements_for_coop_level( + self._campaign.name + ':' + settings['level'] + ) + ) - self._account_type = (ba.internal.get_v1_account_type() - if ba.internal.get_v1_account_state() - == 'signed_in' else None) + self._account_type = ( + ba.internal.get_v1_account_type() + if ba.internal.get_v1_account_state() == 'signed_in' + else None + ) self._game_service_icon_color: Sequence[float] | None self._game_service_achievements_texture: ba.Texture | None @@ -75,10 +79,12 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): self._account_has_achievements = True elif self._account_type == 'Google Play': self._game_service_icon_color = (0.8, 1.0, 0.6) - self._game_service_achievements_texture = ( - ba.gettexture('googlePlayAchievementsIcon')) - self._game_service_leaderboards_texture = ( - ba.gettexture('googlePlayLeaderboardsIcon')) + self._game_service_achievements_texture = ba.gettexture( + 'googlePlayAchievementsIcon' + ) + self._game_service_leaderboards_texture = ba.gettexture( + 'googlePlayLeaderboardsIcon' + ) self._account_has_achievements = True else: self._game_service_icon_color = None @@ -140,8 +146,9 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): self._score_order: str if 'score_order' in settings: if not settings['score_order'] in ['increasing', 'decreasing']: - raise ValueError('Invalid score order: ' + - settings['score_order']) + raise ValueError( + 'Invalid score order: ' + settings['score_order'] + ) self._score_order = settings['score_order'] else: self._score_order = 'increasing' @@ -150,8 +157,9 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): self._score_type: str if 'score_type' in settings: if not settings['score_type'] in ['points', 'time']: - raise ValueError('Invalid score type: ' + - settings['score_type']) + raise ValueError( + 'Invalid score type: ' + settings['score_type'] + ) self._score_type = settings['score_type'] else: self._score_type = 'points' @@ -161,18 +169,24 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): assert isinstance(self._level_name, str) self._game_name_str = self._campaign.name + ':' + self._level_name - self._game_config_str = str(len( - self._playerinfos)) + 'p' + self._campaign.getlevel( - self._level_name).get_score_version_string().replace(' ', '_') + self._game_config_str = ( + str(len(self._playerinfos)) + + 'p' + + self._campaign.getlevel(self._level_name) + .get_score_version_string() + .replace(' ', '_') + ) # If game-center/etc scores are available we show our friends' # scores. Otherwise we show our local high scores. self._show_friend_scores = ba.internal.game_service_has_leaderboard( - self._game_name_str, self._game_config_str) + self._game_name_str, self._game_config_str + ) try: self._old_best_rank = self._campaign.getlevel( - self._level_name).rating + self._level_name + ).rating except Exception: self._old_best_rank = 0.0 @@ -188,14 +202,16 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): def on_transition_in(self) -> None: from bastd.actor import background # FIXME NO BSSTD + ba.set_analytics_screen('Coop Score Screen') super().on_transition_in() - self._background = background.Background(fade_time=0.45, - start_faded=False, - show_logo=True) + self._background = background.Background( + fade_time=0.45, start_faded=False, show_logo=True + ) def _ui_menu(self) -> None: from bastd.ui import specialoffer + if specialoffer.show_offer(): return ba.containerwidget(edit=self._root_ui, transition='out_left') @@ -205,6 +221,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): def _ui_restart(self) -> None: from bastd.ui.tournamententry import TournamentEntryWindow from bastd.ui import specialoffer + if specialoffer.show_offer(): return @@ -214,20 +231,24 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): if self._tournament_time_remaining is None: ba.screenmessage( ba.Lstr(resource='tournamentCheckingStateText'), - color=(1, 0, 0)) + color=(1, 0, 0), + ) ba.playsound(ba.getsound('error')) return if self._tournament_time_remaining <= 0: - ba.screenmessage(ba.Lstr(resource='tournamentEndedText'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr(resource='tournamentEndedText'), color=(1, 0, 0) + ) ba.playsound(ba.getsound('error')) return # If there are currently fewer players than our session min, # don't allow. if len(self.players) < self.session.min_players: - ba.screenmessage(ba.Lstr(resource='notEnoughPlayersRemainingText'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr(resource='notEnoughPlayersRemainingText'), + color=(1, 0, 0), + ) ba.playsound(ba.getsound('error')) return @@ -241,7 +262,8 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): TournamentEntryWindow( tournament_id=tournament_id, tournament_activity=self, - position=self._restart_button.get_screen_space_center()) + position=self._restart_button.get_screen_space_center(), + ) else: ba.containerwidget(edit=self._root_ui, transition='out_left') self.can_show_ad_on_death = True @@ -250,13 +272,17 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): def _ui_next(self) -> None: from bastd.ui.specialoffer import show_offer + if show_offer(): return # If we didn't just complete this level but are choosing to play the # next one, set it as current (this won't happen otherwise). - if (self._is_complete and self._is_more_levels - and not self._newly_complete): + if ( + self._is_complete + and self._is_more_levels + and not self._newly_complete + ): assert self._next_level_name is not None self._campaign.set_selected_level(self._next_level_name) ba.containerwidget(edit=self._root_ui, transition='out_left') @@ -264,9 +290,11 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): self.end({'outcome': 'next_level'}) def _ui_gc(self) -> None: - ba.internal.show_online_score_ui('leaderboard', - game=self._game_name_str, - game_version=self._game_config_str) + ba.internal.show_online_score_ui( + 'leaderboard', + game=self._game_name_str, + game_version=self._game_config_str, + ) def _ui_show_achievements(self) -> None: ba.internal.show_online_score_ui('achievements') @@ -274,8 +302,9 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): def _ui_worlds_best(self) -> None: if self._score_link is None: ba.playsound(ba.getsound('error')) - ba.screenmessage(ba.Lstr(resource='scoreListUnavailableText'), - color=(1, 0.5, 0)) + ba.screenmessage( + ba.Lstr(resource='scoreListUnavailableText'), color=(1, 0.5, 0) + ) else: ba.open_url(self._score_link) @@ -288,12 +317,15 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): scale=0.54, h_align=Text.HAlign.CENTER, color=(0.5, 0.7, 0.5, 1), - position=(300, -235)) + position=(300, -235), + ) ba.playsound(ba.getsound('error')) ba.timer( 2.0, - ba.WeakCall(self._next_level_error.handlemessage, - ba.DieMessage())) + ba.WeakCall( + self._next_level_error.handlemessage, ba.DieMessage() + ), + ) def _should_show_worlds_best_button(self) -> bool: # Link is too complicated to display with no browser. @@ -320,8 +352,9 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): if not self.players: return - rootc = self._root_ui = ba.containerwidget(size=(0, 0), - transition='in_right') + rootc = self._root_ui = ba.containerwidget( + size=(0, 0), transition='in_right' + ) h_offs = 7.0 v_offs = -280.0 @@ -334,31 +367,34 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): ba.internal.set_ui_input_device(None) # Menu is up for grabs. if self._show_friend_scores: - ba.buttonwidget(parent=rootc, - color=(0.45, 0.4, 0.5), - position=(h_offs - 520, v_offs + 480), - size=(300, 60), - label=ba.Lstr(resource='topFriendsText'), - on_activate_call=ba.WeakCall(self._ui_gc), - transition_delay=delay + 0.5, - icon=self._game_service_leaderboards_texture, - icon_color=self._game_service_icon_color, - autoselect=True, - selectable=can_select_extra_buttons) + ba.buttonwidget( + parent=rootc, + color=(0.45, 0.4, 0.5), + position=(h_offs - 520, v_offs + 480), + size=(300, 60), + label=ba.Lstr(resource='topFriendsText'), + on_activate_call=ba.WeakCall(self._ui_gc), + transition_delay=delay + 0.5, + icon=self._game_service_leaderboards_texture, + icon_color=self._game_service_icon_color, + autoselect=True, + selectable=can_select_extra_buttons, + ) if self._have_achievements and self._account_has_achievements: - ba.buttonwidget(parent=rootc, - color=(0.45, 0.4, 0.5), - position=(h_offs - 520, v_offs + 450 - 235 + 40), - size=(300, 60), - label=ba.Lstr(resource='achievementsText'), - on_activate_call=ba.WeakCall( - self._ui_show_achievements), - transition_delay=delay + 1.5, - icon=self._game_service_achievements_texture, - icon_color=self._game_service_icon_color, - autoselect=True, - selectable=can_select_extra_buttons) + ba.buttonwidget( + parent=rootc, + color=(0.45, 0.4, 0.5), + position=(h_offs - 520, v_offs + 450 - 235 + 40), + size=(300, 60), + label=ba.Lstr(resource='achievementsText'), + on_activate_call=ba.WeakCall(self._ui_show_achievements), + transition_delay=delay + 1.5, + icon=self._game_service_achievements_texture, + icon_color=self._game_service_icon_color, + autoselect=True, + selectable=can_select_extra_buttons, + ) if self._should_show_worlds_best_button(): ba.buttonwidget( @@ -367,48 +403,57 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): position=(160, v_offs + 480), size=(350, 62), label=ba.Lstr(resource='tournamentStandingsText') - if self.session.tournament_id is not None else ba.Lstr( - resource='worldsBestScoresText') if self._score_type - == 'points' else ba.Lstr(resource='worldsBestTimesText'), + if self.session.tournament_id is not None + else ba.Lstr(resource='worldsBestScoresText') + if self._score_type == 'points' + else ba.Lstr(resource='worldsBestTimesText'), autoselect=True, on_activate_call=ba.WeakCall(self._ui_worlds_best), transition_delay=delay + 1.9, - selectable=can_select_extra_buttons) + selectable=can_select_extra_buttons, + ) else: pass - show_next_button = self._is_more_levels and not (ba.app.demo_mode - or ba.app.arcade_mode) + show_next_button = self._is_more_levels and not ( + ba.app.demo_mode or ba.app.arcade_mode + ) if not show_next_button: h_offs += 70 - menu_button = ba.buttonwidget(parent=rootc, - autoselect=True, - position=(h_offs - 130 - 60, v_offs), - size=(110, 85), - label='', - on_activate_call=ba.WeakCall( - self._ui_menu)) - ba.imagewidget(parent=rootc, - draw_controller=menu_button, - position=(h_offs - 130 - 60 + 22, v_offs + 14), - size=(60, 60), - texture=self._menu_icon_texture, - opacity=0.8) + menu_button = ba.buttonwidget( + parent=rootc, + autoselect=True, + position=(h_offs - 130 - 60, v_offs), + size=(110, 85), + label='', + on_activate_call=ba.WeakCall(self._ui_menu), + ) + ba.imagewidget( + parent=rootc, + draw_controller=menu_button, + position=(h_offs - 130 - 60 + 22, v_offs + 14), + size=(60, 60), + texture=self._menu_icon_texture, + opacity=0.8, + ) self._restart_button = restart_button = ba.buttonwidget( parent=rootc, autoselect=True, position=(h_offs - 60, v_offs), size=(110, 85), label='', - on_activate_call=ba.WeakCall(self._ui_restart)) - ba.imagewidget(parent=rootc, - draw_controller=restart_button, - position=(h_offs - 60 + 19, v_offs + 7), - size=(70, 70), - texture=self._replay_icon_texture, - opacity=0.8) + on_activate_call=ba.WeakCall(self._ui_restart), + ) + ba.imagewidget( + parent=rootc, + draw_controller=restart_button, + position=(h_offs - 60 + 19, v_offs + 7), + size=(70, 70), + texture=self._replay_icon_texture, + opacity=0.8, + ) next_button: ba.Widget | None = None @@ -425,24 +470,30 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): button_sound = False image_opacity = 0.2 color = (0.3, 0.3, 0.3) - next_button = ba.buttonwidget(parent=rootc, - autoselect=True, - position=(h_offs + 130 - 60, v_offs), - size=(110, 85), - label='', - on_activate_call=call, - color=color, - enable_sound=button_sound) - ba.imagewidget(parent=rootc, - draw_controller=next_button, - position=(h_offs + 130 - 60 + 12, v_offs + 5), - size=(80, 80), - texture=self._next_level_icon_texture, - opacity=image_opacity) + next_button = ba.buttonwidget( + parent=rootc, + autoselect=True, + position=(h_offs + 130 - 60, v_offs), + size=(110, 85), + label='', + on_activate_call=call, + color=color, + enable_sound=button_sound, + ) + ba.imagewidget( + parent=rootc, + draw_controller=next_button, + position=(h_offs + 130 - 60 + 12, v_offs + 5), + size=(80, 80), + texture=self._next_level_icon_texture, + opacity=image_opacity, + ) x_offs_extra = 0 if show_next_button else -100 - self._corner_button_offs = (h_offs + 300.0 + 100.0 + x_offs_extra, - v_offs + 560.0) + self._corner_button_offs = ( + h_offs + 300.0 + 100.0 + x_offs_extra, + v_offs + 560.0, + ) if ba.app.demo_mode or ba.app.arcade_mode: self._league_rank_button = None @@ -456,7 +507,8 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): color=(0.4, 0.4, 0.9), textcolor=(0.9, 0.9, 2.0), transition_delay=0.0, - smooth_update_delay=5.0) + smooth_update_delay=5.0, + ) self._store_button_instance = StoreButton( parent=rootc, position=(h_offs + 400 + 100 + x_offs_extra, v_offs + 560), @@ -467,20 +519,24 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): button_type='square', color=(0.35, 0.25, 0.45), textcolor=(0.9, 0.7, 1.0), - transition_delay=0.0) + transition_delay=0.0, + ) - ba.containerwidget(edit=rootc, - selected_child=next_button if - (self._newly_complete and self._victory - and show_next_button) else restart_button, - on_cancel_call=menu_button.activate) + ba.containerwidget( + edit=rootc, + selected_child=next_button + if (self._newly_complete and self._victory and show_next_button) + else restart_button, + on_cancel_call=menu_button.activate, + ) self._update_corner_button_positions() self._update_corner_button_positions_timer = ba.Timer( 1.0, ba.WeakCall(self._update_corner_button_positions), repeat=True, - timetype=ba.TimeType.REAL) + timetype=ba.TimeType.REAL, + ) def _update_corner_button_positions(self) -> None: offs = -55 if ba.internal.is_party_icon_visible() else 0 @@ -497,8 +553,11 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): # If this activity is a good 'end point', ask server-mode just once if # it wants to do anything special like switch sessions or kill the app. - if (self._allow_server_transition and ba.app.server is not None - and self._server_transitioning is None): + if ( + self._allow_server_transition + and ba.app.server is not None + and self._server_transitioning is None + ): self._server_transitioning = ba.app.server.handle_transition() assert isinstance(self._server_transitioning, bool) @@ -518,9 +577,14 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): # (though theoretically that should be ok). if not self.is_transitioning_out() and player: player.assigninput( - (ba.InputType.JUMP_PRESS, ba.InputType.PUNCH_PRESS, - ba.InputType.BOMB_PRESS, ba.InputType.PICK_UP_PRESS), - self._player_press) + ( + ba.InputType.JUMP_PRESS, + ba.InputType.PUNCH_PRESS, + ba.InputType.BOMB_PRESS, + ba.InputType.PICK_UP_PRESS, + ), + self._player_press, + ) def on_player_join(self, player: ba.Player) -> None: super().on_player_join(player) @@ -528,7 +592,8 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): if ba.app.server is not None: # Host can't press retry button, so anyone can do it instead. time_till_assign = max( - 0, self._birth_time + self._min_view_time - ba.time()) + 0, self._birth_time + self._min_view_time - ba.time() + ) ba.timer(time_till_assign, ba.WeakCall(self._safe_assign, player)) @@ -545,123 +610,156 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): levels = self._campaign.levels level = self._campaign.getlevel(self._level_name) self._was_complete = level.complete - self._is_complete = (self._was_complete or self._victory) - self._newly_complete = (self._is_complete and not self._was_complete) - self._is_more_levels = ((level.index < len(levels) - 1) - and self._campaign.sequential) + self._is_complete = self._was_complete or self._victory + self._newly_complete = self._is_complete and not self._was_complete + self._is_more_levels = ( + level.index < len(levels) - 1 + ) and self._campaign.sequential # Any time we complete a level, set the next one as unlocked. if self._is_complete and self._is_more_levels: - ba.internal.add_transaction({ - 'type': 'COMPLETE_LEVEL', - 'campaign': self._campaign.name, - 'level': self._level_name - }) + ba.internal.add_transaction( + { + 'type': 'COMPLETE_LEVEL', + 'campaign': self._campaign.name, + 'level': self._level_name, + } + ) self._next_level_name = levels[level.index + 1].name # If this is the first time we completed it, set the next one # as current. if self._newly_complete: cfg = ba.app.config - cfg['Selected Coop Game'] = (self._campaign.name + ':' + - self._next_level_name) + cfg['Selected Coop Game'] = ( + self._campaign.name + ':' + self._next_level_name + ) cfg.commit() self._campaign.set_selected_level(self._next_level_name) ba.timer(1.0, ba.WeakCall(self.request_ui)) - if (self._is_complete and self._victory and self._is_more_levels - and not (ba.app.demo_mode or ba.app.arcade_mode)): - Text(ba.Lstr(value='${A}:\n', - subs=[('${A}', ba.Lstr(resource='levelUnlockedText')) - ]) if self._newly_complete else - ba.Lstr(value='${A}:\n', - subs=[('${A}', ba.Lstr(resource='nextLevelText'))]), - transition=Text.Transition.IN_RIGHT, - transition_delay=5.2, - flash=self._newly_complete, - scale=0.54, - h_align=Text.HAlign.CENTER, - maxwidth=270, - color=(0.5, 0.7, 0.5, 1), - position=(270, -235)).autoretain() + if ( + self._is_complete + and self._victory + and self._is_more_levels + and not (ba.app.demo_mode or ba.app.arcade_mode) + ): + Text( + ba.Lstr( + value='${A}:\n', + subs=[('${A}', ba.Lstr(resource='levelUnlockedText'))], + ) + if self._newly_complete + else ba.Lstr( + value='${A}:\n', + subs=[('${A}', ba.Lstr(resource='nextLevelText'))], + ), + transition=Text.Transition.IN_RIGHT, + transition_delay=5.2, + flash=self._newly_complete, + scale=0.54, + h_align=Text.HAlign.CENTER, + maxwidth=270, + color=(0.5, 0.7, 0.5, 1), + position=(270, -235), + ).autoretain() assert self._next_level_name is not None - Text(ba.Lstr(translate=('coopLevelNames', self._next_level_name)), - transition=Text.Transition.IN_RIGHT, - transition_delay=5.2, - flash=self._newly_complete, - scale=0.7, - h_align=Text.HAlign.CENTER, - maxwidth=205, - color=(0.5, 0.7, 0.5, 1), - position=(270, -255)).autoretain() + Text( + ba.Lstr(translate=('coopLevelNames', self._next_level_name)), + transition=Text.Transition.IN_RIGHT, + transition_delay=5.2, + flash=self._newly_complete, + scale=0.7, + h_align=Text.HAlign.CENTER, + maxwidth=205, + color=(0.5, 0.7, 0.5, 1), + position=(270, -255), + ).autoretain() if self._newly_complete: ba.timer(5.2, ba.Call(ba.playsound, self._cashregistersound)) ba.timer(5.2, ba.Call(ba.playsound, self._dingsound)) offs_x = -195 if len(self._playerinfos) > 1: - pstr = ba.Lstr(value='- ${A} -', - subs=[('${A}', - ba.Lstr(resource='multiPlayerCountText', - subs=[('${COUNT}', - str(len(self._playerinfos))) - ]))]) + pstr = ba.Lstr( + value='- ${A} -', + subs=[ + ( + '${A}', + ba.Lstr( + resource='multiPlayerCountText', + subs=[('${COUNT}', str(len(self._playerinfos)))], + ), + ) + ], + ) else: - pstr = ba.Lstr(value='- ${A} -', - subs=[('${A}', - ba.Lstr(resource='singlePlayerCountText'))]) - ZoomText(self._campaign.getlevel(self._level_name).displayname, - maxwidth=800, - flash=False, - trail=False, - color=(0.5, 1, 0.5, 1), - h_align='center', - scale=0.4, - position=(0, 292), - jitter=1.0).autoretain() - Text(pstr, - maxwidth=300, - transition=Text.Transition.FADE_IN, - scale=0.7, - h_align=Text.HAlign.CENTER, - v_align=Text.VAlign.CENTER, - color=(0.5, 0.7, 0.5, 1), - position=(0, 230)).autoretain() + pstr = ba.Lstr( + value='- ${A} -', + subs=[('${A}', ba.Lstr(resource='singlePlayerCountText'))], + ) + ZoomText( + self._campaign.getlevel(self._level_name).displayname, + maxwidth=800, + flash=False, + trail=False, + color=(0.5, 1, 0.5, 1), + h_align='center', + scale=0.4, + position=(0, 292), + jitter=1.0, + ).autoretain() + Text( + pstr, + maxwidth=300, + transition=Text.Transition.FADE_IN, + scale=0.7, + h_align=Text.HAlign.CENTER, + v_align=Text.VAlign.CENTER, + color=(0.5, 0.7, 0.5, 1), + position=(0, 230), + ).autoretain() if ba.app.server is None: # If we're running in normal non-headless build, show this text # because only host can continue the game. adisp = ba.internal.get_v1_account_display_string() - txt = Text(ba.Lstr(resource='waitingForHostText', - subs=[('${HOST}', adisp)]), - maxwidth=300, - transition=Text.Transition.FADE_IN, - transition_delay=8.0, - scale=0.85, - h_align=Text.HAlign.CENTER, - v_align=Text.VAlign.CENTER, - color=(1, 1, 0, 1), - position=(0, -230)).autoretain() + txt = Text( + ba.Lstr( + resource='waitingForHostText', subs=[('${HOST}', adisp)] + ), + maxwidth=300, + transition=Text.Transition.FADE_IN, + transition_delay=8.0, + scale=0.85, + h_align=Text.HAlign.CENTER, + v_align=Text.VAlign.CENTER, + color=(1, 1, 0, 1), + position=(0, -230), + ).autoretain() assert txt.node txt.node.client_only = True else: # In headless build, anyone can continue the game. sval = ba.Lstr(resource='pressAnyButtonPlayAgainText') - Text(sval, - v_attach=Text.VAttach.BOTTOM, - h_align=Text.HAlign.CENTER, - flash=True, - vr_depth=50, - position=(0, 60), - scale=0.8, - color=(0.5, 0.7, 0.5, 0.5), - transition=Text.Transition.IN_BOTTOM_SLOW, - transition_delay=self._min_view_time).autoretain() + Text( + sval, + v_attach=Text.VAttach.BOTTOM, + h_align=Text.HAlign.CENTER, + flash=True, + vr_depth=50, + position=(0, 60), + scale=0.8, + color=(0.5, 0.7, 0.5, 0.5), + transition=Text.Transition.IN_BOTTOM_SLOW, + transition_delay=self._min_view_time, + ).autoretain() if self._score is not None: - ba.timer(0.35, - ba.Call(ba.playsound, self._score_display_sound_small)) + ba.timer( + 0.35, ba.Call(ba.playsound, self._score_display_sound_small) + ) # Vestigial remain; this stuff should just be instance vars. self._show_info = {} @@ -672,51 +770,63 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): ba.pushcall(ba.WeakCall(self._show_fail)) self._name_str = name_str = ', '.join( - [p.name for p in self._playerinfos]) + [p.name for p in self._playerinfos] + ) if self._show_friend_scores: self._friends_loading_status = Text( - ba.Lstr(value='${A}...', - subs=[('${A}', ba.Lstr(resource='loadingText'))]), + ba.Lstr( + value='${A}...', + subs=[('${A}', ba.Lstr(resource='loadingText'))], + ), position=(-405, 150 + 30), color=(1, 1, 1, 0.4), transition=Text.Transition.FADE_IN, scale=0.7, - transition_delay=2.0) - self._score_loading_status = Text(ba.Lstr( - value='${A}...', subs=[('${A}', ba.Lstr(resource='loadingText'))]), - position=(280, 150 + 30), - color=(1, 1, 1, 0.4), - transition=Text.Transition.FADE_IN, - scale=0.7, - transition_delay=2.0) + transition_delay=2.0, + ) + self._score_loading_status = Text( + ba.Lstr( + value='${A}...', + subs=[('${A}', ba.Lstr(resource='loadingText'))], + ), + position=(280, 150 + 30), + color=(1, 1, 1, 0.4), + transition=Text.Transition.FADE_IN, + scale=0.7, + transition_delay=2.0, + ) if self._score is not None: ba.timer(0.4, ba.WeakCall(self._play_drumroll)) # Add us to high scores, filter, and store. our_high_scores_all = self._campaign.getlevel( - self._level_name).get_high_scores() + self._level_name + ).get_high_scores() our_high_scores = our_high_scores_all.setdefault( - str(len(self._playerinfos)) + ' Player', []) + str(len(self._playerinfos)) + ' Player', [] + ) if self._score is not None: our_score: list | None = [ - self._score, { - 'players': [{ - 'name': p.name, - 'character': p.character - } for p in self._playerinfos] - } + self._score, + { + 'players': [ + {'name': p.name, 'character': p.character} + for p in self._playerinfos + ] + }, ] our_high_scores.append(our_score) else: our_score = None try: - our_high_scores.sort(reverse=self._score_order == 'increasing', - key=lambda x: x[0]) + our_high_scores.sort( + reverse=self._score_order == 'increasing', key=lambda x: x[0] + ) except Exception: ba.print_exception('Error sorting scores.') print(f'our_high_scores: {our_high_scores}') @@ -724,15 +834,18 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): del our_high_scores[10:] if self._score is not None: - sver = (self._campaign.getlevel( - self._level_name).get_score_version_string()) - ba.internal.add_transaction({ - 'type': 'SET_LEVEL_LOCAL_HIGH_SCORES', - 'campaign': self._campaign.name, - 'level': self._level_name, - 'scoreVersion': sver, - 'scores': our_high_scores_all - }) + sver = self._campaign.getlevel( + self._level_name + ).get_score_version_string() + ba.internal.add_transaction( + { + 'type': 'SET_LEVEL_LOCAL_HIGH_SCORES', + 'campaign': self._campaign.name, + 'level': self._level_name, + 'scoreVersion': sver, + 'scores': our_high_scores_all, + } + ) if ba.internal.get_v1_account_state() != 'signed_in': # We expect this only in kiosk mode; complain otherwise. if not (ba.app.demo_mode or ba.app.arcade_mode): @@ -750,12 +863,14 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): self._score, ba.WeakCall(self._got_score_results), ba.WeakCall(self._got_friend_score_results) - if self._show_friend_scores else None, + if self._show_friend_scores + else None, order=self._score_order, tournament_id=self.session.tournament_id, score_type=self._score_type, campaign=self._campaign.name, - level=self._level_name) + level=self._level_name, + ) # Apply the transactions we've been adding locally. ba.internal.run_transactions() @@ -765,16 +880,19 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): ts_height = 300 ts_h_offs = 210 v_offs = 40 - txt = Text(ba.Lstr(resource='tournamentStandingsText') - if self.session.tournament_id is not None else ba.Lstr( - resource='worldsBestScoresText') if self._score_type - == 'points' else ba.Lstr(resource='worldsBestTimesText'), - maxwidth=210, - position=(ts_h_offs - 10, ts_height / 2 + 25 + v_offs + 20), - transition=Text.Transition.IN_LEFT, - v_align=Text.VAlign.CENTER, - scale=1.2, - transition_delay=2.2).autoretain() + txt = Text( + ba.Lstr(resource='tournamentStandingsText') + if self.session.tournament_id is not None + else ba.Lstr(resource='worldsBestScoresText') + if self._score_type == 'points' + else ba.Lstr(resource='worldsBestTimesText'), + maxwidth=210, + position=(ts_h_offs - 10, ts_height / 2 + 25 + v_offs + 20), + transition=Text.Transition.IN_LEFT, + v_align=Text.VAlign.CENTER, + scale=1.2, + transition_delay=2.2, + ).autoretain() # If we've got a button on the server, only show this on clients. if self._should_show_worlds_best_button(): @@ -788,14 +906,15 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): ts_height = 300 ts_h_offs = -480 v_offs = 40 - txt = Text(ba.Lstr(resource='topFriendsText'), - maxwidth=210, - position=(ts_h_offs - 10, - ts_height / 2 + 25 + v_offs + 20), - transition=Text.Transition.IN_RIGHT, - v_align=Text.VAlign.CENTER, - scale=1.2, - transition_delay=1.8).autoretain() + txt = Text( + ba.Lstr(resource='topFriendsText'), + maxwidth=210, + position=(ts_h_offs - 10, ts_height / 2 + 25 + v_offs + 20), + transition=Text.Transition.IN_RIGHT, + v_align=Text.VAlign.CENTER, + scale=1.2, + transition_delay=1.8, + ).autoretain() assert txt.node txt.node.client_only = True else: @@ -803,14 +922,17 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): ts_height = 300 ts_h_offs = -480 v_offs = 40 - Text(ba.Lstr(resource='yourBestScoresText') if self._score_type - == 'points' else ba.Lstr(resource='yourBestTimesText'), - maxwidth=210, - position=(ts_h_offs - 10, ts_height / 2 + 25 + v_offs + 20), - transition=Text.Transition.IN_RIGHT, - v_align=Text.VAlign.CENTER, - scale=1.2, - transition_delay=1.8).autoretain() + Text( + ba.Lstr(resource='yourBestScoresText') + if self._score_type == 'points' + else ba.Lstr(resource='yourBestTimesText'), + maxwidth=210, + position=(ts_h_offs - 10, ts_height / 2 + 25 + v_offs + 20), + transition=Text.Transition.IN_RIGHT, + v_align=Text.VAlign.CENTER, + scale=1.2, + transition_delay=1.8, + ).autoretain() display_scores = list(our_high_scores) display_count = 5 @@ -835,21 +957,23 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): scale = 0.5 times: list[tuple[float, float]] = [] for i in range(display_count): - times.insert(random.randrange(0, - len(times) + 1), - (1.9 + i * 0.05, 2.3 + i * 0.05)) + times.insert( + random.randrange(0, len(times) + 1), + (1.9 + i * 0.05, 2.3 + i * 0.05), + ) for i in range(display_count): try: if display_scores[i][1] is None: name_str = '-' else: # noinspection PyUnresolvedReferences - name_str = ', '.join([ - p['name'] for p in display_scores[i][1]['players'] - ]) + name_str = ', '.join( + [p['name'] for p in display_scores[i][1]['players']] + ) except Exception: ba.print_exception( - f'Error calcing name_str for {display_scores}') + f'Error calcing name_str for {display_scores}' + ) name_str = '-' if display_scores[i] == our_score and not showed_ours: flash = True @@ -864,31 +988,49 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): color1 = (0.6, 0.6, 0.6, 1.0) tdelay1 = times[i][0] tdelay2 = times[i][1] - Text(str(display_scores[i][0]) if self._score_type == 'points' - else ba.timestring(display_scores[i][0] * 10, - timeformat=ba.TimeFormat.MILLISECONDS, - suppress_format_warning=True), - position=(ts_h_offs + 20 + h_offs_extra, - v_offs_extra + ts_height / 2 + -ts_height * - (i + 1) / 10 + v_offs + 11.0), - h_align=Text.HAlign.RIGHT, - v_align=Text.VAlign.CENTER, - color=color0, - flash=flash, - transition=Text.Transition.IN_RIGHT, - transition_delay=tdelay1).autoretain() + Text( + str(display_scores[i][0]) + if self._score_type == 'points' + else ba.timestring( + display_scores[i][0] * 10, + timeformat=ba.TimeFormat.MILLISECONDS, + suppress_format_warning=True, + ), + position=( + ts_h_offs + 20 + h_offs_extra, + v_offs_extra + + ts_height / 2 + + -ts_height * (i + 1) / 10 + + v_offs + + 11.0, + ), + h_align=Text.HAlign.RIGHT, + v_align=Text.VAlign.CENTER, + color=color0, + flash=flash, + transition=Text.Transition.IN_RIGHT, + transition_delay=tdelay1, + ).autoretain() - Text(ba.Lstr(value=name_str), - position=(ts_h_offs + 35 + h_offs_extra, - v_offs_extra + ts_height / 2 + -ts_height * - (i + 1) / 10 + v_offs_names + v_offs + 11.0), - maxwidth=80.0 + 100.0 * len(self._playerinfos), - v_align=Text.VAlign.CENTER, - color=color1, - flash=flash, - scale=scale, - transition=Text.Transition.IN_RIGHT, - transition_delay=tdelay2).autoretain() + Text( + ba.Lstr(value=name_str), + position=( + ts_h_offs + 35 + h_offs_extra, + v_offs_extra + + ts_height / 2 + + -ts_height * (i + 1) / 10 + + v_offs_names + + v_offs + + 11.0, + ), + maxwidth=80.0 + 100.0 * len(self._playerinfos), + v_align=Text.VAlign.CENTER, + color=color1, + flash=flash, + scale=scale, + transition=Text.Transition.IN_RIGHT, + transition_delay=tdelay2, + ).autoretain() # Show achievements for this level. ts_height = -150 @@ -900,19 +1042,21 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): # version, etc). if self._have_achievements: if not self._account_has_achievements: - Text(ba.Lstr(resource='achievementsText'), - position=(ts_h_offs - 10, - ts_height / 2 + 25 + v_offs + 3), - maxwidth=210, - host_only=True, - transition=Text.Transition.IN_RIGHT, - v_align=Text.VAlign.CENTER, - scale=1.2, - transition_delay=2.8).autoretain() + Text( + ba.Lstr(resource='achievementsText'), + position=(ts_h_offs - 10, ts_height / 2 + 25 + v_offs + 3), + maxwidth=210, + host_only=True, + transition=Text.Transition.IN_RIGHT, + v_align=Text.VAlign.CENTER, + scale=1.2, + transition_delay=2.8, + ).autoretain() assert self._game_name_str is not None achievements = ba.app.ach.achievements_for_coop_level( - self._game_name_str) + self._game_name_str + ) hval = -455 vval = -100 tdelay = 0.0 @@ -925,12 +1069,15 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): def _play_drumroll(self) -> None: ba.NodeActor( - ba.newnode('sound', - attrs={ - 'sound': self.drum_roll_sound, - 'positional': False, - 'loop': False - })).autoretain() + ba.newnode( + 'sound', + attrs={ + 'sound': self.drum_roll_sound, + 'positional': False, + 'loop': False, + }, + ) + ).autoretain() def _got_friend_score_results(self, results: list[Any] | None) -> None: @@ -939,6 +1086,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): # pylint: disable=too-many-branches # pylint: disable=too-many-statements from efro.util import asserttype + # delay a bit if results come in too fast assert self._begin_time is not None base_delay = max(0, 1.9 - (ba.time() - self._begin_time)) @@ -955,7 +1103,8 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): color=(1, 1, 1, 0.4), transition=Text.Transition.FADE_IN, transition_delay=base_delay + 0.8, - scale=0.7) + scale=0.7, + ) return self._friends_loading_status = None @@ -974,8 +1123,10 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): results.remove(score) break results.append(our_score_entry) - results.sort(reverse=self._score_order == 'increasing', - key=lambda x: asserttype(x[0], int)) + results.sort( + reverse=self._score_order == 'increasing', + key=lambda x: asserttype(x[0], int), + ) # If we're not submitting our own score, we still want to change the # name of our own score to 'Me'. @@ -995,9 +1146,10 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): results = results[:5] times: list[tuple[float, float]] = [] for i in range(len(results)): - times.insert(random.randrange(0, - len(times) + 1), - (base_delay + i * 0.05, base_delay + 0.3 + i * 0.05)) + times.insert( + random.randrange(0, len(times) + 1), + (base_delay + i * 0.05, base_delay + 0.3 + i * 0.05), + ) for i, tval in enumerate(results): score = int(tval[0]) name_str = tval[1] @@ -1019,33 +1171,50 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): tdelay1 = times[i][0] tdelay2 = times[i][1] if name_str != '-': - Text(str(score) if self._score_type == 'points' else - ba.timestring(score * 10, - timeformat=ba.TimeFormat.MILLISECONDS), - position=(ts_h_offs + 20 + h_offs_extra, - v_offs_extra + ts_height / 2 + -ts_height * - (i + 1) / 10 + v_offs + 11.0), - h_align=Text.HAlign.RIGHT, - v_align=Text.VAlign.CENTER, - color=color0, - flash=flash, - transition=Text.Transition.IN_RIGHT, - transition_delay=tdelay1).autoretain() + Text( + str(score) + if self._score_type == 'points' + else ba.timestring( + score * 10, timeformat=ba.TimeFormat.MILLISECONDS + ), + position=( + ts_h_offs + 20 + h_offs_extra, + v_offs_extra + + ts_height / 2 + + -ts_height * (i + 1) / 10 + + v_offs + + 11.0, + ), + h_align=Text.HAlign.RIGHT, + v_align=Text.VAlign.CENTER, + color=color0, + flash=flash, + transition=Text.Transition.IN_RIGHT, + transition_delay=tdelay1, + ).autoretain() else: if is_me: print('Error: got empty name_str on score result:', tval) - Text(ba.Lstr(value=name_str), - position=(ts_h_offs + 35 + h_offs_extra, - v_offs_extra + ts_height / 2 + -ts_height * - (i + 1) / 10 + v_offs_names + v_offs + 11.0), - color=color1, - maxwidth=160.0, - v_align=Text.VAlign.CENTER, - flash=flash, - scale=scale, - transition=Text.Transition.IN_RIGHT, - transition_delay=tdelay2).autoretain() + Text( + ba.Lstr(value=name_str), + position=( + ts_h_offs + 35 + h_offs_extra, + v_offs_extra + + ts_height / 2 + + -ts_height * (i + 1) / 10 + + v_offs_names + + v_offs + + 11.0, + ), + color=color1, + maxwidth=160.0, + v_align=Text.VAlign.CENTER, + flash=flash, + scale=scale, + transition=Text.Transition.IN_RIGHT, + transition_delay=tdelay2, + ).autoretain() def _got_score_results(self, results: dict[str, Any] | None) -> None: @@ -1071,16 +1240,20 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): color=(1, 1, 1, 0.4), transition=Text.Transition.FADE_IN, transition_delay=base_delay + 0.3, - scale=0.7) + scale=0.7, + ) else: self._score_link = results['link'] assert self._score_link is not None # Prepend our master-server addr if its a relative addr. - if (not self._score_link.startswith('http://') - and not self._score_link.startswith('https://')): + if not self._score_link.startswith( + 'http://' + ) and not self._score_link.startswith('https://'): self._score_link = ( - ba.internal.get_master_server_address() + '/' + - self._score_link) + ba.internal.get_master_server_address() + + '/' + + self._score_link + ) self._score_loading_status = None if 'tournamentSecondsRemaining' in results: secs_remaining = results['tournamentSecondsRemaining'] @@ -1089,9 +1262,11 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): self._tournament_time_remaining_text_timer = ba.Timer( 1.0, ba.WeakCall( - self._update_tournament_time_remaining_text), + self._update_tournament_time_remaining_text + ), repeat=True, - timetype=ba.TimeType.BASE) + timetype=ba.TimeType.BASE, + ) assert self._show_info is not None self._show_info['results'] = results @@ -1101,11 +1276,13 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): else: self._show_info['tops'] = [] offs_x = -195 - available = (self._show_info['results'] is not None) + available = self._show_info['results'] is not None if self._score is not None: - ba.timer((1.5 + base_delay), - ba.WeakCall(self._show_world_rank, offs_x), - timetype=ba.TimeType.BASE) + ba.timer( + (1.5 + base_delay), + ba.WeakCall(self._show_world_rank, offs_x), + timetype=ba.TimeType.BASE, + ) ts_h_offs = 200 ts_height = 300 @@ -1115,17 +1292,25 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): # Show the number of games represented by this # list (except for in tournaments). if self.session.tournament_id is None: - Text(ba.Lstr(resource='lastGamesText', - subs=[ - ('${COUNT}', - str(self._show_info['results']['total'])) - ]), - position=(ts_h_offs - 35 + 95, - ts_height / 2 + 6 + v_offs), - color=(0.4, 0.4, 0.4, 1.0), - scale=0.7, - transition=Text.Transition.IN_RIGHT, - transition_delay=base_delay + 0.3).autoretain() + Text( + ba.Lstr( + resource='lastGamesText', + subs=[ + ( + '${COUNT}', + str(self._show_info['results']['total']), + ) + ], + ), + position=( + ts_h_offs - 35 + 95, + ts_height / 2 + 6 + v_offs, + ), + color=(0.4, 0.4, 0.4, 1.0), + scale=0.7, + transition=Text.Transition.IN_RIGHT, + transition_delay=base_delay + 0.3, + ).autoretain() else: v_offs += 20 @@ -1151,9 +1336,9 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): times: list[tuple[float, float]] = [] for i in range(len(self._show_info['tops'])): times.insert( - random.randrange(0, - len(times) + 1), - (base_delay + i * 0.05, base_delay + 0.4 + i * 0.05)) + random.randrange(0, len(times) + 1), + (base_delay + i * 0.05, base_delay + 0.4 + i * 0.05), + ) for i, tval in enumerate(self._show_info['tops']): score = int(tval[0]) name_str = tval[1] @@ -1175,44 +1360,63 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): tdelay2 = times[i][1] if name_str != '-': - Text(str(score) if self._score_type == 'points' else - ba.timestring( - score * 10, - timeformat=ba.TimeFormat.MILLISECONDS), - position=(ts_h_offs + 20 + h_offs_extra, - ts_height / 2 + -ts_height * - (i + 1) / 10 + v_offs + 11.0), - h_align=Text.HAlign.RIGHT, - v_align=Text.VAlign.CENTER, - color=color0, - flash=flash, - transition=Text.Transition.IN_LEFT, - transition_delay=tdelay1).autoretain() - Text(ba.Lstr(value=name_str), - position=(ts_h_offs + 35 + h_offs_extra, - ts_height / 2 + -ts_height * (i + 1) / 10 + - v_offs_names + v_offs + 11.0), - maxwidth=80.0 + 100.0 * len(self._playerinfos), - v_align=Text.VAlign.CENTER, - color=color1, - flash=flash, - scale=scale, - transition=Text.Transition.IN_LEFT, - transition_delay=tdelay2).autoretain() + Text( + str(score) + if self._score_type == 'points' + else ba.timestring( + score * 10, + timeformat=ba.TimeFormat.MILLISECONDS, + ), + position=( + ts_h_offs + 20 + h_offs_extra, + ts_height / 2 + + -ts_height * (i + 1) / 10 + + v_offs + + 11.0, + ), + h_align=Text.HAlign.RIGHT, + v_align=Text.VAlign.CENTER, + color=color0, + flash=flash, + transition=Text.Transition.IN_LEFT, + transition_delay=tdelay1, + ).autoretain() + Text( + ba.Lstr(value=name_str), + position=( + ts_h_offs + 35 + h_offs_extra, + ts_height / 2 + + -ts_height * (i + 1) / 10 + + v_offs_names + + v_offs + + 11.0, + ), + maxwidth=80.0 + 100.0 * len(self._playerinfos), + v_align=Text.VAlign.CENTER, + color=color1, + flash=flash, + scale=scale, + transition=Text.Transition.IN_LEFT, + transition_delay=tdelay2, + ).autoretain() def _show_tips(self) -> None: from bastd.actor.tipstext import TipsText + TipsText(offs_y=30).autoretain() def _update_tournament_time_remaining_text(self) -> None: if self._tournament_time_remaining is None: return self._tournament_time_remaining = max( - 0, self._tournament_time_remaining - 1) + 0, self._tournament_time_remaining - 1 + ) if self._tournament_time_remaining_text is not None: - val = ba.timestring(self._tournament_time_remaining, - suppress_format_warning=True, - centi=False) + val = ba.timestring( + self._tournament_time_remaining, + suppress_format_warning=True, + centi=False, + ) self._tournament_time_remaining_text.node.text = val def _show_world_rank(self, offs_x: float) -> None: @@ -1221,16 +1425,23 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): # pylint: disable=too-many-branches # pylint: disable=too-many-statements from ba.internal import get_tournament_prize_strings + assert self._show_info is not None - available = (self._show_info['results'] is not None) + available = self._show_info['results'] is not None if available: - error = (self._show_info['results']['error'] - if 'error' in self._show_info['results'] else None) + error = ( + self._show_info['results']['error'] + if 'error' in self._show_info['results'] + else None + ) rank = self._show_info['results']['rank'] total = self._show_info['results']['total'] - rating = (10.0 if total == 1 else 10.0 * (1.0 - (float(rank - 1) / - (total - 1)))) + rating = ( + 10.0 + if total == 1 + else 10.0 * (1.0 - (float(rank - 1) / (total - 1))) + ) player_rank = self._show_info['results']['playerRank'] best_player_rank = self._show_info['results']['bestPlayerRank'] else: @@ -1241,15 +1452,17 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): # If we've got tournament-seconds-remaining, show it. if self._tournament_time_remaining is not None: - Text(ba.Lstr(resource='coopSelectWindow.timeRemainingText'), - position=(-360, -70 - 100), - color=(1, 1, 1, 0.7), - h_align=Text.HAlign.CENTER, - v_align=Text.VAlign.CENTER, - transition=Text.Transition.FADE_IN, - scale=0.8, - maxwidth=300, - transition_delay=2.0).autoretain() + Text( + ba.Lstr(resource='coopSelectWindow.timeRemainingText'), + position=(-360, -70 - 100), + color=(1, 1, 1, 0.7), + h_align=Text.HAlign.CENTER, + v_align=Text.VAlign.CENTER, + transition=Text.Transition.FADE_IN, + scale=0.8, + maxwidth=300, + transition_delay=2.0, + ).autoretain() self._tournament_time_remaining_text = Text( '', position=(-360, -110 - 100), @@ -1259,7 +1472,8 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): transition=Text.Transition.FADE_IN, scale=1.6, maxwidth=150, - transition_delay=2.0) + transition_delay=2.0, + ) # If we're a tournament, show prizes. try: @@ -1267,105 +1481,135 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): if tournament_id is not None: if tournament_id in ba.app.accounts_v1.tournament_info: tourney_info = ba.app.accounts_v1.tournament_info[ - tournament_id] + tournament_id + ] # pylint: disable=unbalanced-tuple-unpacking - pr1, pv1, pr2, pv2, pr3, pv3 = ( - get_tournament_prize_strings(tourney_info)) + pr1, pv1, pr2, pv2, pr3, pv3 = get_tournament_prize_strings( + tourney_info + ) # pylint: enable=unbalanced-tuple-unpacking - Text(ba.Lstr(resource='coopSelectWindow.prizesText'), - position=(-360, -70 + 77), - color=(1, 1, 1, 0.7), - h_align=Text.HAlign.CENTER, - v_align=Text.VAlign.CENTER, - transition=Text.Transition.FADE_IN, - scale=1.0, - maxwidth=300, - transition_delay=2.0).autoretain() + Text( + ba.Lstr(resource='coopSelectWindow.prizesText'), + position=(-360, -70 + 77), + color=(1, 1, 1, 0.7), + h_align=Text.HAlign.CENTER, + v_align=Text.VAlign.CENTER, + transition=Text.Transition.FADE_IN, + scale=1.0, + maxwidth=300, + transition_delay=2.0, + ).autoretain() vval = -107 + 70 for rng, val in ((pr1, pv1), (pr2, pv2), (pr3, pv3)): - Text(rng, - position=(-410 + 10, vval), - color=(1, 1, 1, 0.7), - h_align=Text.HAlign.RIGHT, - v_align=Text.VAlign.CENTER, - transition=Text.Transition.FADE_IN, - scale=0.6, - maxwidth=300, - transition_delay=2.0).autoretain() - Text(val, - position=(-390 + 10, vval), - color=(0.7, 0.7, 0.7, 1.0), - h_align=Text.HAlign.LEFT, - v_align=Text.VAlign.CENTER, - transition=Text.Transition.FADE_IN, - scale=0.8, - maxwidth=300, - transition_delay=2.0).autoretain() + Text( + rng, + position=(-410 + 10, vval), + color=(1, 1, 1, 0.7), + h_align=Text.HAlign.RIGHT, + v_align=Text.VAlign.CENTER, + transition=Text.Transition.FADE_IN, + scale=0.6, + maxwidth=300, + transition_delay=2.0, + ).autoretain() + Text( + val, + position=(-390 + 10, vval), + color=(0.7, 0.7, 0.7, 1.0), + h_align=Text.HAlign.LEFT, + v_align=Text.VAlign.CENTER, + transition=Text.Transition.FADE_IN, + scale=0.8, + maxwidth=300, + transition_delay=2.0, + ).autoretain() vval -= 35 except Exception: ba.print_exception('Error showing prize ranges.') if self._do_new_rating: if error: - ZoomText(ba.Lstr(resource='failText'), - flash=True, - trail=True, - scale=1.0 if available else 0.333, - tilt_translate=0.11, - h_align='center', - position=(190 + offs_x, -60), - maxwidth=200, - jitter=1.0).autoretain() - Text(ba.Lstr(translate=('serverResponses', error)), - position=(0, -140), - color=(1, 1, 1, 0.7), - h_align=Text.HAlign.CENTER, - v_align=Text.VAlign.CENTER, - transition=Text.Transition.FADE_IN, - scale=0.9, - maxwidth=400, - transition_delay=1.0).autoretain() + ZoomText( + ba.Lstr(resource='failText'), + flash=True, + trail=True, + scale=1.0 if available else 0.333, + tilt_translate=0.11, + h_align='center', + position=(190 + offs_x, -60), + maxwidth=200, + jitter=1.0, + ).autoretain() + Text( + ba.Lstr(translate=('serverResponses', error)), + position=(0, -140), + color=(1, 1, 1, 0.7), + h_align=Text.HAlign.CENTER, + v_align=Text.VAlign.CENTER, + transition=Text.Transition.FADE_IN, + scale=0.9, + maxwidth=400, + transition_delay=1.0, + ).autoretain() else: - ZoomText((('#' + str(player_rank)) if player_rank is not None - else ba.Lstr(resource='unavailableText')), - flash=True, - trail=True, - scale=1.0 if available else 0.333, - tilt_translate=0.11, - h_align='center', - position=(190 + offs_x, -60), - maxwidth=200, - jitter=1.0).autoretain() + ZoomText( + ( + ('#' + str(player_rank)) + if player_rank is not None + else ba.Lstr(resource='unavailableText') + ), + flash=True, + trail=True, + scale=1.0 if available else 0.333, + tilt_translate=0.11, + h_align='center', + position=(190 + offs_x, -60), + maxwidth=200, + jitter=1.0, + ).autoretain() - Text(ba.Lstr(value='${A}:', - subs=[('${A}', ba.Lstr(resource='rankText'))]), - position=(0, 36), - maxwidth=300, - transition=Text.Transition.FADE_IN, - h_align=Text.HAlign.CENTER, - v_align=Text.VAlign.CENTER, - transition_delay=0).autoretain() + Text( + ba.Lstr( + value='${A}:', + subs=[('${A}', ba.Lstr(resource='rankText'))], + ), + position=(0, 36), + maxwidth=300, + transition=Text.Transition.FADE_IN, + h_align=Text.HAlign.CENTER, + v_align=Text.VAlign.CENTER, + transition_delay=0, + ).autoretain() if best_player_rank is not None: - Text(ba.Lstr(resource='currentStandingText', - fallback_resource='bestRankText', - subs=[('${RANK}', str(best_player_rank))]), - position=(0, -155), - color=(1, 1, 1, 0.7), - h_align=Text.HAlign.CENTER, - transition=Text.Transition.FADE_IN, - scale=0.7, - transition_delay=1.0).autoretain() + Text( + ba.Lstr( + resource='currentStandingText', + fallback_resource='bestRankText', + subs=[('${RANK}', str(best_player_rank))], + ), + position=(0, -155), + color=(1, 1, 1, 0.7), + h_align=Text.HAlign.CENTER, + transition=Text.Transition.FADE_IN, + scale=0.7, + transition_delay=1.0, + ).autoretain() else: - ZoomText((f'{rating:.1f}' if available else ba.Lstr( - resource='unavailableText')), - flash=True, - trail=True, - scale=0.6 if available else 0.333, - tilt_translate=0.11, - h_align='center', - position=(190 + offs_x, -94), - maxwidth=200, - jitter=1.0).autoretain() + ZoomText( + ( + f'{rating:.1f}' + if available + else ba.Lstr(resource='unavailableText') + ), + flash=True, + trail=True, + scale=0.6 if available else 0.333, + tilt_translate=0.11, + h_align='center', + position=(190 + offs_x, -94), + maxwidth=200, + jitter=1.0, + ).autoretain() if available: if rating >= 9.5: @@ -1380,56 +1624,68 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): star_x = 135 + offs_x for _i in range(stars): img = ba.NodeActor( - ba.newnode('image', - attrs={ - 'texture': star_tex, - 'position': (star_x, -16), - 'scale': (62, 62), - 'opacity': 1.0, - 'color': (2.2, 1.2, 0.3), - 'absolute_scale': True - })).autoretain() + ba.newnode( + 'image', + attrs={ + 'texture': star_tex, + 'position': (star_x, -16), + 'scale': (62, 62), + 'opacity': 1.0, + 'color': (2.2, 1.2, 0.3), + 'absolute_scale': True, + }, + ) + ).autoretain() assert img.node ba.animate(img.node, 'opacity', {0.15: 0, 0.4: 1}) star_x += 60 for _i in range(3 - stars): img = ba.NodeActor( - ba.newnode('image', - attrs={ - 'texture': star_tex, - 'position': (star_x, -16), - 'scale': (62, 62), - 'opacity': 1.0, - 'color': (0.3, 0.3, 0.3), - 'absolute_scale': True - })).autoretain() + ba.newnode( + 'image', + attrs={ + 'texture': star_tex, + 'position': (star_x, -16), + 'scale': (62, 62), + 'opacity': 1.0, + 'color': (0.3, 0.3, 0.3), + 'absolute_scale': True, + }, + ) + ).autoretain() assert img.node ba.animate(img.node, 'opacity', {0.15: 0, 0.4: 1}) star_x += 60 - def dostar(count: int, xval: float, offs_y: float, - score: str) -> None: - Text(score + ' =', - position=(xval, -64 + offs_y), - color=(0.6, 0.6, 0.6, 0.6), - h_align=Text.HAlign.CENTER, - v_align=Text.VAlign.CENTER, - transition=Text.Transition.FADE_IN, - scale=0.4, - transition_delay=1.0).autoretain() + def dostar( + count: int, xval: float, offs_y: float, score: str + ) -> None: + Text( + score + ' =', + position=(xval, -64 + offs_y), + color=(0.6, 0.6, 0.6, 0.6), + h_align=Text.HAlign.CENTER, + v_align=Text.VAlign.CENTER, + transition=Text.Transition.FADE_IN, + scale=0.4, + transition_delay=1.0, + ).autoretain() stx = xval + 20 for _i2 in range(count): img2 = ba.NodeActor( - ba.newnode('image', - attrs={ - 'texture': star_tex, - 'position': (stx, -64 + offs_y), - 'scale': (12, 12), - 'opacity': 0.7, - 'color': (2.2, 1.2, 0.3), - 'absolute_scale': True - })).autoretain() + ba.newnode( + 'image', + attrs={ + 'texture': star_tex, + 'position': (stx, -64 + offs_y), + 'scale': (12, 12), + 'opacity': 0.7, + 'color': (2.2, 1.2, 0.3), + 'absolute_scale': True, + }, + ) + ).autoretain() assert img2.node ba.animate(img2.node, 'opacity', {1.0: 0.0, 1.5: 0.5}) stx += 13.0 @@ -1443,95 +1699,138 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): best_rank = 0.0 if available: - Text(ba.Lstr( - resource='outOfText', - subs=[('${RANK}', - str(int(self._show_info['results']['rank']))), - ('${ALL}', str(self._show_info['results']['total'])) - ]), - position=(0, -155 if self._newly_complete else -145), - color=(1, 1, 1, 0.7), - h_align=Text.HAlign.CENTER, - transition=Text.Transition.FADE_IN, - scale=0.55, - transition_delay=1.0).autoretain() + Text( + ba.Lstr( + resource='outOfText', + subs=[ + ( + '${RANK}', + str(int(self._show_info['results']['rank'])), + ), + ( + '${ALL}', + str(self._show_info['results']['total']), + ), + ], + ), + position=(0, -155 if self._newly_complete else -145), + color=(1, 1, 1, 0.7), + h_align=Text.HAlign.CENTER, + transition=Text.Transition.FADE_IN, + scale=0.55, + transition_delay=1.0, + ).autoretain() - new_best = (best_rank > self._old_best_rank and best_rank > 0.0) - was_string = ba.Lstr(value=' ${A}', - subs=[('${A}', - ba.Lstr(resource='scoreWasText')), - ('${COUNT}', str(self._old_best_rank))]) + new_best = best_rank > self._old_best_rank and best_rank > 0.0 + was_string = ba.Lstr( + value=' ${A}', + subs=[ + ('${A}', ba.Lstr(resource='scoreWasText')), + ('${COUNT}', str(self._old_best_rank)), + ], + ) if not self._newly_complete: - Text(ba.Lstr(value='${A}${B}', - subs=[('${A}', - ba.Lstr(resource='newPersonalBestText')), - ('${B}', was_string)]) if new_best else - ba.Lstr(resource='bestRatingText', - subs=[('${RATING}', str(best_rank))]), - position=(0, -165), - color=(1, 1, 1, 0.7), - flash=new_best, - h_align=Text.HAlign.CENTER, - transition=(Text.Transition.IN_RIGHT - if new_best else Text.Transition.FADE_IN), - scale=0.5, - transition_delay=1.0).autoretain() + Text( + ba.Lstr( + value='${A}${B}', + subs=[ + ('${A}', ba.Lstr(resource='newPersonalBestText')), + ('${B}', was_string), + ], + ) + if new_best + else ba.Lstr( + resource='bestRatingText', + subs=[('${RATING}', str(best_rank))], + ), + position=(0, -165), + color=(1, 1, 1, 0.7), + flash=new_best, + h_align=Text.HAlign.CENTER, + transition=( + Text.Transition.IN_RIGHT + if new_best + else Text.Transition.FADE_IN + ), + scale=0.5, + transition_delay=1.0, + ).autoretain() - Text(ba.Lstr(value='${A}:', - subs=[('${A}', ba.Lstr(resource='ratingText'))]), - position=(0, 36), - maxwidth=300, - transition=Text.Transition.FADE_IN, - h_align=Text.HAlign.CENTER, - v_align=Text.VAlign.CENTER, - transition_delay=0).autoretain() + Text( + ba.Lstr( + value='${A}:', + subs=[('${A}', ba.Lstr(resource='ratingText'))], + ), + position=(0, 36), + maxwidth=300, + transition=Text.Transition.FADE_IN, + h_align=Text.HAlign.CENTER, + v_align=Text.VAlign.CENTER, + transition_delay=0, + ).autoretain() ba.timer(0.35, ba.Call(ba.playsound, self._score_display_sound)) if not error: ba.timer(0.35, ba.Call(ba.playsound, self.cymbal_sound)) def _show_fail(self) -> None: - ZoomText(ba.Lstr(resource='failText'), - maxwidth=300, - flash=False, - trail=True, - h_align='center', - tilt_translate=0.11, - position=(0, 40), - jitter=1.0).autoretain() + ZoomText( + ba.Lstr(resource='failText'), + maxwidth=300, + flash=False, + trail=True, + h_align='center', + tilt_translate=0.11, + position=(0, 40), + jitter=1.0, + ).autoretain() if self._fail_message is not None: - Text(self._fail_message, - h_align=Text.HAlign.CENTER, - position=(0, -130), - maxwidth=300, - color=(1, 1, 1, 0.5), - transition=Text.Transition.FADE_IN, - transition_delay=1.0).autoretain() + Text( + self._fail_message, + h_align=Text.HAlign.CENTER, + position=(0, -130), + maxwidth=300, + color=(1, 1, 1, 0.5), + transition=Text.Transition.FADE_IN, + transition_delay=1.0, + ).autoretain() ba.timer(0.35, ba.Call(ba.playsound, self._score_display_sound)) def _show_score_val(self, offs_x: float) -> None: assert self._score_type is not None assert self._score is not None - ZoomText((str(self._score) if self._score_type == 'points' else - ba.timestring(self._score * 10, - timeformat=ba.TimeFormat.MILLISECONDS)), - maxwidth=300, - flash=True, - trail=True, - scale=1.0 if self._score_type == 'points' else 0.6, - h_align='center', - tilt_translate=0.11, - position=(190 + offs_x, 115), - jitter=1.0).autoretain() - Text(ba.Lstr( - value='${A}:', subs=[('${A}', ba.Lstr( - resource='finalScoreText'))]) if self._score_type == 'points' - else ba.Lstr(value='${A}:', - subs=[('${A}', ba.Lstr(resource='finalTimeText'))]), - maxwidth=300, - position=(0, 200), - transition=Text.Transition.FADE_IN, - h_align=Text.HAlign.CENTER, - v_align=Text.VAlign.CENTER, - transition_delay=0).autoretain() + ZoomText( + ( + str(self._score) + if self._score_type == 'points' + else ba.timestring( + self._score * 10, timeformat=ba.TimeFormat.MILLISECONDS + ) + ), + maxwidth=300, + flash=True, + trail=True, + scale=1.0 if self._score_type == 'points' else 0.6, + h_align='center', + tilt_translate=0.11, + position=(190 + offs_x, 115), + jitter=1.0, + ).autoretain() + Text( + ba.Lstr( + value='${A}:', + subs=[('${A}', ba.Lstr(resource='finalScoreText'))], + ) + if self._score_type == 'points' + else ba.Lstr( + value='${A}:', + subs=[('${A}', ba.Lstr(resource='finalTimeText'))], + ), + maxwidth=300, + position=(0, 200), + transition=Text.Transition.FADE_IN, + h_align=Text.HAlign.CENTER, + v_align=Text.VAlign.CENTER, + transition_delay=0, + ).autoretain() ba.timer(0.35, ba.Call(ba.playsound, self._score_display_sound)) diff --git a/dist/ba_data/python/bastd/activity/drawscore.py b/dist/ba_data/python/bastd/activity/drawscore.py index 38109f7..c9a8c95 100644 --- a/dist/ba_data/python/bastd/activity/drawscore.py +++ b/dist/ba_data/python/bastd/activity/drawscore.py @@ -22,13 +22,15 @@ class DrawScoreScreenActivity(MultiTeamScoreScreenActivity): def on_begin(self) -> None: ba.set_analytics_screen('Draw Score Screen') super().on_begin() - ZoomText(ba.Lstr(resource='drawText'), - position=(0, 0), - maxwidth=400, - shiftposition=(-220, 0), - shiftdelay=2.0, - flash=False, - trail=False, - jitter=1.0).autoretain() + ZoomText( + ba.Lstr(resource='drawText'), + position=(0, 0), + maxwidth=400, + shiftposition=(-220, 0), + shiftdelay=2.0, + flash=False, + trail=False, + jitter=1.0, + ).autoretain() ba.timer(0.35, ba.Call(ba.playsound, self._score_display_sound)) self.show_player_scores(results=self.settings_raw.get('results', None)) diff --git a/dist/ba_data/python/bastd/activity/dualteamscore.py b/dist/ba_data/python/bastd/activity/dualteamscore.py index aae7fde..d00bae8 100644 --- a/dist/ba_data/python/bastd/activity/dualteamscore.py +++ b/dist/ba_data/python/bastd/activity/dualteamscore.py @@ -37,91 +37,132 @@ class TeamVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): session = self.session assert isinstance(session, ba.MultiTeamSession) if ba.app.lang.get_resource('bestOfUseFirstToInstead'): - best_txt = ba.Lstr(resource='firstToSeriesText', - subs=[('${COUNT}', - str(session.get_series_length() / 2 + 1)) - ]) + best_txt = ba.Lstr( + resource='firstToSeriesText', + subs=[('${COUNT}', str(session.get_series_length() / 2 + 1))], + ) else: - best_txt = ba.Lstr(resource='bestOfSeriesText', - subs=[('${COUNT}', - str(session.get_series_length()))]) + best_txt = ba.Lstr( + resource='bestOfSeriesText', + subs=[('${COUNT}', str(session.get_series_length()))], + ) - ZoomText(best_txt, - position=(0, 175), - shiftposition=(-250, 175), - shiftdelay=2.5, - flash=False, - trail=False, - h_align='center', - scale=0.25, - color=(0.5, 0.5, 0.5, 1.0), - jitter=3.0).autoretain() + ZoomText( + best_txt, + position=(0, 175), + shiftposition=(-250, 175), + shiftdelay=2.5, + flash=False, + trail=False, + h_align='center', + scale=0.25, + color=(0.5, 0.5, 0.5, 1.0), + jitter=3.0, + ).autoretain() for team in self.session.sessionteams: ba.timer( i * 0.15 + 0.15, - ba.WeakCall(self._show_team_name, vval - i * height, team, - i * 0.2, shift_time - (i * 0.150 + 0.150))) - ba.timer(i * 0.150 + 0.5, - ba.Call(ba.playsound, self._score_display_sound_small)) - scored = (team is self._winner) + ba.WeakCall( + self._show_team_name, + vval - i * height, + team, + i * 0.2, + shift_time - (i * 0.150 + 0.150), + ), + ) + ba.timer( + i * 0.150 + 0.5, + ba.Call(ba.playsound, self._score_display_sound_small), + ) + scored = team is self._winner delay = 0.2 if scored: delay = 1.2 ba.timer( i * 0.150 + 0.2, - ba.WeakCall(self._show_team_old_score, vval - i * height, - team, shift_time - (i * 0.15 + 0.2))) - ba.timer(i * 0.15 + 1.5, - ba.Call(ba.playsound, self._score_display_sound)) + ba.WeakCall( + self._show_team_old_score, + vval - i * height, + team, + shift_time - (i * 0.15 + 0.2), + ), + ) + ba.timer( + i * 0.15 + 1.5, + ba.Call(ba.playsound, self._score_display_sound), + ) ba.timer( i * 0.150 + delay, - ba.WeakCall(self._show_team_score, vval - i * height, team, - scored, i * 0.2 + 0.1, - shift_time - (i * 0.15 + delay))) + ba.WeakCall( + self._show_team_score, + vval - i * height, + team, + scored, + i * 0.2 + 0.1, + shift_time - (i * 0.15 + delay), + ), + ) i += 1 self.show_player_scores() - def _show_team_name(self, pos_v: float, team: ba.SessionTeam, - kill_delay: float, shiftdelay: float) -> None: + def _show_team_name( + self, + pos_v: float, + team: ba.SessionTeam, + kill_delay: float, + shiftdelay: float, + ) -> None: del kill_delay # Unused arg. - ZoomText(ba.Lstr(value='${A}:', subs=[('${A}', team.name)]), - position=(100, pos_v), - shiftposition=(-150, pos_v), - shiftdelay=shiftdelay, - flash=False, - trail=False, - h_align='right', - maxwidth=300, - color=team.color, - jitter=1.0).autoretain() + ZoomText( + ba.Lstr(value='${A}:', subs=[('${A}', team.name)]), + position=(100, pos_v), + shiftposition=(-150, pos_v), + shiftdelay=shiftdelay, + flash=False, + trail=False, + h_align='right', + maxwidth=300, + color=team.color, + jitter=1.0, + ).autoretain() - def _show_team_old_score(self, pos_v: float, sessionteam: ba.SessionTeam, - shiftdelay: float) -> None: - ZoomText(str(sessionteam.customdata['score'] - 1), - position=(150, pos_v), - maxwidth=100, - color=(0.6, 0.6, 0.7), - shiftposition=(-100, pos_v), - shiftdelay=shiftdelay, - flash=False, - trail=False, - lifespan=1.0, - h_align='left', - jitter=1.0).autoretain() + def _show_team_old_score( + self, pos_v: float, sessionteam: ba.SessionTeam, shiftdelay: float + ) -> None: + ZoomText( + str(sessionteam.customdata['score'] - 1), + position=(150, pos_v), + maxwidth=100, + color=(0.6, 0.6, 0.7), + shiftposition=(-100, pos_v), + shiftdelay=shiftdelay, + flash=False, + trail=False, + lifespan=1.0, + h_align='left', + jitter=1.0, + ).autoretain() - def _show_team_score(self, pos_v: float, sessionteam: ba.SessionTeam, - scored: bool, kill_delay: float, - shiftdelay: float) -> None: + def _show_team_score( + self, + pos_v: float, + sessionteam: ba.SessionTeam, + scored: bool, + kill_delay: float, + shiftdelay: float, + ) -> None: del kill_delay # Unused arg. - ZoomText(str(sessionteam.customdata['score']), - position=(150, pos_v), - maxwidth=100, - color=(1.0, 0.9, 0.5) if scored else (0.6, 0.6, 0.7), - shiftposition=(-100, pos_v), - shiftdelay=shiftdelay, - flash=scored, - trail=scored, - h_align='left', - jitter=1.0, - trailcolor=(1, 0.8, 0.0, 0)).autoretain() + ZoomText( + str(sessionteam.customdata['score']), + position=(150, pos_v), + maxwidth=100, + color=(1.0, 0.9, 0.5) if scored else (0.6, 0.6, 0.7), + shiftposition=(-100, pos_v), + shiftdelay=shiftdelay, + flash=scored, + trail=scored, + h_align='left', + jitter=1.0, + trailcolor=(1, 0.8, 0.0, 0), + ).autoretain() diff --git a/dist/ba_data/python/bastd/activity/freeforallvictory.py b/dist/ba_data/python/bastd/activity/freeforallvictory.py index 5161c2b..d4a5c58 100644 --- a/dist/ba_data/python/bastd/activity/freeforallvictory.py +++ b/dist/ba_data/python/bastd/activity/freeforallvictory.py @@ -28,6 +28,7 @@ class FreeForAllVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): # pylint: disable=too-many-statements from bastd.actor.text import Text from bastd.actor.image import Image + ba.set_analytics_screen('FreeForAll Score Screen') super().on_begin() @@ -45,14 +46,17 @@ class FreeForAllVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): key=lambda p: ( p.team.sessionteam.customdata['previous_score'], p.getname(full=True), - )) + ), + ) player_order = list(self.players) - player_order.sort(reverse=True, - key=lambda p: ( - p.team.sessionteam.customdata['score'], - p.team.sessionteam.customdata['score'], - p.getname(full=True), - )) + player_order.sort( + reverse=True, + key=lambda p: ( + p.team.sessionteam.customdata['score'], + p.team.sessionteam.customdata['score'], + p.getname(full=True), + ), + ) v_offs = -74.0 + spacing * len(player_order_prev) * 0.5 delay1 = 1.3 + 0.1 @@ -66,30 +70,36 @@ class FreeForAllVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): ba.timer(0.3, ba.Call(ba.playsound, self._score_display_sound)) results = self.settings_raw['results'] assert isinstance(results, ba.GameResults) - self.show_player_scores(delay=0.001, - results=results, - scale=1.2, - x_offset=-110.0) + self.show_player_scores( + delay=0.001, results=results, scale=1.2, x_offset=-110.0 + ) sound_times: set[float] = set() - def _scoretxt(text: str, - x_offs: float, - y_offs: float, - highlight: bool, - delay: float, - extrascale: float, - flash: bool = False) -> Text: - return Text(text, - position=(ts_h_offs + x_offs * scale, - y_base + (y_offs + v_offs + 2.0) * scale), - scale=scale * extrascale, - color=((1.0, 0.7, 0.3, 1.0) if highlight else - (0.7, 0.7, 0.7, 0.7)), - h_align=Text.HAlign.RIGHT, - transition=Text.Transition.IN_LEFT, - transition_delay=tdelay + delay, - flash=flash).autoretain() + def _scoretxt( + text: str, + x_offs: float, + y_offs: float, + highlight: bool, + delay: float, + extrascale: float, + flash: bool = False, + ) -> Text: + return Text( + text, + position=( + ts_h_offs + x_offs * scale, + y_base + (y_offs + v_offs + 2.0) * scale, + ), + scale=scale * extrascale, + color=( + (1.0, 0.7, 0.3, 1.0) if highlight else (0.7, 0.7, 0.7, 0.7) + ), + h_align=Text.HAlign.RIGHT, + transition=Text.Transition.IN_LEFT, + transition_delay=tdelay + delay, + flash=flash, + ).autoretain() v_offs -= spacing slide_amt = 0.0 @@ -98,16 +108,21 @@ class FreeForAllVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): session = self.session assert isinstance(session, ba.FreeForAllSession) - title = Text(ba.Lstr(resource='firstToSeriesText', - subs=[('${COUNT}', - str(session.get_ffa_series_length()))]), - scale=1.05 * scale, - position=(ts_h_offs - 0.0 * scale, - y_base + (v_offs + 50.0) * scale), - h_align=Text.HAlign.CENTER, - color=(0.5, 0.5, 0.5, 0.5), - transition=Text.Transition.IN_LEFT, - transition_delay=tdelay).autoretain() + title = Text( + ba.Lstr( + resource='firstToSeriesText', + subs=[('${COUNT}', str(session.get_ffa_series_length()))], + ), + scale=1.05 * scale, + position=( + ts_h_offs - 0.0 * scale, + y_base + (v_offs + 50.0) * scale, + ), + h_align=Text.HAlign.CENTER, + color=(0.5, 0.5, 0.5, 0.5), + transition=Text.Transition.IN_LEFT, + transition_delay=tdelay, + ).autoretain() v_offs -= 25 v_offs_start = v_offs @@ -115,152 +130,239 @@ class FreeForAllVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): ba.timer( tdelay + delay3, ba.WeakCall( - self._safe_animate, title.position_combine, 'input0', { + self._safe_animate, + title.position_combine, + 'input0', + { 0.0: ts_h_offs - 0.0 * scale, - transtime2: ts_h_offs - (0.0 + slide_amt) * scale - })) + transtime2: ts_h_offs - (0.0 + slide_amt) * scale, + }, + ), + ) for i, player in enumerate(player_order_prev): v_offs_2 = v_offs_start - spacing * (player_order.index(player)) - ba.timer(tdelay + 0.3, - ba.Call(ba.playsound, self._score_display_sound_small)) + ba.timer( + tdelay + 0.3, + ba.Call(ba.playsound, self._score_display_sound_small), + ) if order_change: - ba.timer(tdelay + delay2 + 0.1, - ba.Call(ba.playsound, self._cymbal_sound)) - img = Image(player.get_icon(), - position=(ts_h_offs - 72.0 * scale, - y_base + (v_offs + 15.0) * scale), - scale=(30.0 * scale, 30.0 * scale), - transition=Image.Transition.IN_LEFT, - transition_delay=tdelay).autoretain() + ba.timer( + tdelay + delay2 + 0.1, + ba.Call(ba.playsound, self._cymbal_sound), + ) + img = Image( + player.get_icon(), + position=( + ts_h_offs - 72.0 * scale, + y_base + (v_offs + 15.0) * scale, + ), + scale=(30.0 * scale, 30.0 * scale), + transition=Image.Transition.IN_LEFT, + transition_delay=tdelay, + ).autoretain() ba.timer( tdelay + delay2, ba.WeakCall( - self._safe_animate, img.position_combine, 'input1', { + self._safe_animate, + img.position_combine, + 'input1', + { 0: y_base + (v_offs + 15.0) * scale, - transtime: y_base + (v_offs_2 + 15.0) * scale - })) + transtime: y_base + (v_offs_2 + 15.0) * scale, + }, + ), + ) ba.timer( tdelay + delay3, ba.WeakCall( - self._safe_animate, img.position_combine, 'input0', { + self._safe_animate, + img.position_combine, + 'input0', + { 0: ts_h_offs - 72.0 * scale, - transtime2: ts_h_offs - (72.0 + slide_amt) * scale - })) - txt = Text(ba.Lstr(value=player.getname(full=True)), - maxwidth=130.0, - scale=0.75 * scale, - position=(ts_h_offs - 50.0 * scale, - y_base + (v_offs + 15.0) * scale), - h_align=Text.HAlign.LEFT, - v_align=Text.VAlign.CENTER, - color=ba.safecolor(player.team.color + (1, )), - transition=Text.Transition.IN_LEFT, - transition_delay=tdelay).autoretain() + transtime2: ts_h_offs - (72.0 + slide_amt) * scale, + }, + ), + ) + txt = Text( + ba.Lstr(value=player.getname(full=True)), + maxwidth=130.0, + scale=0.75 * scale, + position=( + ts_h_offs - 50.0 * scale, + y_base + (v_offs + 15.0) * scale, + ), + h_align=Text.HAlign.LEFT, + v_align=Text.VAlign.CENTER, + color=ba.safecolor(player.team.color + (1,)), + transition=Text.Transition.IN_LEFT, + transition_delay=tdelay, + ).autoretain() ba.timer( tdelay + delay2, ba.WeakCall( - self._safe_animate, txt.position_combine, 'input1', { + self._safe_animate, + txt.position_combine, + 'input1', + { 0: y_base + (v_offs + 15.0) * scale, - transtime: y_base + (v_offs_2 + 15.0) * scale - })) + transtime: y_base + (v_offs_2 + 15.0) * scale, + }, + ), + ) ba.timer( tdelay + delay3, ba.WeakCall( - self._safe_animate, txt.position_combine, 'input0', { + self._safe_animate, + txt.position_combine, + 'input0', + { 0: ts_h_offs - 50.0 * scale, - transtime2: ts_h_offs - (50.0 + slide_amt) * scale - })) + transtime2: ts_h_offs - (50.0 + slide_amt) * scale, + }, + ), + ) - txt_num = Text('#' + str(i + 1), - scale=0.55 * scale, - position=(ts_h_offs - 95.0 * scale, - y_base + (v_offs + 8.0) * scale), - h_align=Text.HAlign.RIGHT, - color=(0.6, 0.6, 0.6, 0.6), - transition=Text.Transition.IN_LEFT, - transition_delay=tdelay).autoretain() + txt_num = Text( + '#' + str(i + 1), + scale=0.55 * scale, + position=( + ts_h_offs - 95.0 * scale, + y_base + (v_offs + 8.0) * scale, + ), + h_align=Text.HAlign.RIGHT, + color=(0.6, 0.6, 0.6, 0.6), + transition=Text.Transition.IN_LEFT, + transition_delay=tdelay, + ).autoretain() ba.timer( tdelay + delay3, ba.WeakCall( - self._safe_animate, txt_num.position_combine, 'input0', { + self._safe_animate, + txt_num.position_combine, + 'input0', + { 0: ts_h_offs - 95.0 * scale, - transtime2: ts_h_offs - (95.0 + slide_amt) * scale - })) + transtime2: ts_h_offs - (95.0 + slide_amt) * scale, + }, + ), + ) s_txt = _scoretxt( - str(player.team.sessionteam.customdata['previous_score']), 80, - 0, False, 0, 1.0) + str(player.team.sessionteam.customdata['previous_score']), + 80, + 0, + False, + 0, + 1.0, + ) ba.timer( tdelay + delay2, ba.WeakCall( - self._safe_animate, s_txt.position_combine, 'input1', { + self._safe_animate, + s_txt.position_combine, + 'input1', + { 0: y_base + (v_offs + 2.0) * scale, - transtime: y_base + (v_offs_2 + 2.0) * scale - })) + transtime: y_base + (v_offs_2 + 2.0) * scale, + }, + ), + ) ba.timer( tdelay + delay3, ba.WeakCall( - self._safe_animate, s_txt.position_combine, 'input0', { + self._safe_animate, + s_txt.position_combine, + 'input0', + { 0: ts_h_offs + 80.0 * scale, - transtime2: ts_h_offs + (80.0 - slide_amt) * scale - })) + transtime2: ts_h_offs + (80.0 - slide_amt) * scale, + }, + ), + ) score_change = ( - player.team.sessionteam.customdata['score'] - - player.team.sessionteam.customdata['previous_score']) + player.team.sessionteam.customdata['score'] + - player.team.sessionteam.customdata['previous_score'] + ) if score_change > 0: xval = 113 yval = 3.0 - s_txt_2 = _scoretxt('+' + str(score_change), - xval, - yval, - True, - 0, - 0.7, - flash=True) + s_txt_2 = _scoretxt( + '+' + str(score_change), + xval, + yval, + True, + 0, + 0.7, + flash=True, + ) ba.timer( tdelay + delay2, ba.WeakCall( - self._safe_animate, s_txt_2.position_combine, 'input1', + self._safe_animate, + s_txt_2.position_combine, + 'input1', { 0: y_base + (v_offs + yval + 2.0) * scale, - transtime: y_base + (v_offs_2 + yval + 2.0) * scale - })) + transtime: y_base + (v_offs_2 + yval + 2.0) * scale, + }, + ), + ) ba.timer( tdelay + delay3, ba.WeakCall( - self._safe_animate, s_txt_2.position_combine, 'input0', + self._safe_animate, + s_txt_2.position_combine, + 'input0', { 0: ts_h_offs + xval * scale, - transtime2: ts_h_offs + (xval - slide_amt) * scale - })) + transtime2: ts_h_offs + (xval - slide_amt) * scale, + }, + ), + ) - def _safesetattr(node: ba.Node | None, attr: str, - value: Any) -> None: + def _safesetattr( + node: ba.Node | None, attr: str, value: Any + ) -> None: if node: setattr(node, attr, value) ba.timer( tdelay + delay1, - ba.Call(_safesetattr, s_txt.node, 'color', (1, 1, 1, 1))) + ba.Call(_safesetattr, s_txt.node, 'color', (1, 1, 1, 1)), + ) for j in range(score_change): - ba.timer((tdelay + delay1 + 0.15 * j), - ba.Call( - _safesetattr, s_txt.node, 'text', - str(player.team.sessionteam. - customdata['previous_score'] + j + 1))) + ba.timer( + (tdelay + delay1 + 0.15 * j), + ba.Call( + _safesetattr, + s_txt.node, + 'text', + str( + player.team.sessionteam.customdata[ + 'previous_score' + ] + + j + + 1 + ), + ), + ) tfin = tdelay + delay1 + 0.15 * j if tfin not in sound_times: sound_times.add(tfin) ba.timer( tfin, - ba.Call(ba.playsound, - self._score_display_sound_small)) + ba.Call( + ba.playsound, self._score_display_sound_small + ), + ) v_offs -= spacing - def _safe_animate(self, node: ba.Node | None, attr: str, - keys: dict[float, float]) -> None: + def _safe_animate( + self, node: ba.Node | None, attr: str, keys: dict[float, float] + ) -> None: """Run an animation on a node if the node still exists.""" if node: ba.animate(node, attr, keys) diff --git a/dist/ba_data/python/bastd/activity/multiteamjoin.py b/dist/ba_data/python/bastd/activity/multiteamjoin.py index 92e75f8..ae2e7e5 100644 --- a/dist/ba_data/python/bastd/activity/multiteamjoin.py +++ b/dist/ba_data/python/bastd/activity/multiteamjoin.py @@ -24,6 +24,7 @@ class MultiTeamJoinActivity(JoinActivity): def on_transition_in(self) -> None: from bastd.actor.controlsguide import ControlsGuide from ba import DualTeamSession + super().on_transition_in() ControlsGuide(delay=1.0).autoretain() @@ -31,50 +32,62 @@ class MultiTeamJoinActivity(JoinActivity): assert isinstance(session, ba.MultiTeamSession) # Show info about the next up game. - self._next_up_text = Text(ba.Lstr( - value='${1} ${2}', - subs=[('${1}', ba.Lstr(resource='upFirstText')), - ('${2}', session.get_next_game_description())]), - h_attach=Text.HAttach.CENTER, - scale=0.7, - v_attach=Text.VAttach.TOP, - h_align=Text.HAlign.CENTER, - position=(0, -70), - flash=False, - color=(0.5, 0.5, 0.5, 1.0), - transition=Text.Transition.FADE_IN, - transition_delay=5.0) + self._next_up_text = Text( + ba.Lstr( + value='${1} ${2}', + subs=[ + ('${1}', ba.Lstr(resource='upFirstText')), + ('${2}', session.get_next_game_description()), + ], + ), + h_attach=Text.HAttach.CENTER, + scale=0.7, + v_attach=Text.VAttach.TOP, + h_align=Text.HAlign.CENTER, + position=(0, -70), + flash=False, + color=(0.5, 0.5, 0.5, 1.0), + transition=Text.Transition.FADE_IN, + transition_delay=5.0, + ) # In teams mode, show our two team names. # FIXME: Lobby should handle this. if isinstance(ba.getsession(), DualTeamSession): team_names = [team.name for team in ba.getsession().sessionteams] team_colors = [ - tuple(team.color) + (0.5, ) + tuple(team.color) + (0.5,) for team in ba.getsession().sessionteams ] if len(team_names) == 2: for i in range(2): - Text(team_names[i], - scale=0.7, - h_attach=Text.HAttach.CENTER, - v_attach=Text.VAttach.TOP, - h_align=Text.HAlign.CENTER, - position=(-200 + 350 * i, -100), - color=team_colors[i], - transition=Text.Transition.FADE_IN).autoretain() + Text( + team_names[i], + scale=0.7, + h_attach=Text.HAttach.CENTER, + v_attach=Text.VAttach.TOP, + h_align=Text.HAlign.CENTER, + position=(-200 + 350 * i, -100), + color=team_colors[i], + transition=Text.Transition.FADE_IN, + ).autoretain() - Text(ba.Lstr(resource='mustInviteFriendsText', - subs=[('${GATHER}', - ba.Lstr(resource='gatherWindow.titleText'))]), - h_attach=Text.HAttach.CENTER, - scale=0.8, - host_only=True, - v_attach=Text.VAttach.CENTER, - h_align=Text.HAlign.CENTER, - position=(0, 0), - flash=False, - color=(0, 1, 0, 1.0), - transition=Text.Transition.FADE_IN, - transition_delay=2.0, - transition_out_delay=7.0).autoretain() + Text( + ba.Lstr( + resource='mustInviteFriendsText', + subs=[ + ('${GATHER}', ba.Lstr(resource='gatherWindow.titleText')) + ], + ), + h_attach=Text.HAttach.CENTER, + scale=0.8, + host_only=True, + v_attach=Text.VAttach.CENTER, + h_align=Text.HAlign.CENTER, + position=(0, 0), + flash=False, + color=(0, 1, 0, 1.0), + transition=Text.Transition.FADE_IN, + transition_delay=2.0, + transition_out_delay=7.0, + ).autoretain() diff --git a/dist/ba_data/python/bastd/activity/multiteamscore.py b/dist/ba_data/python/bastd/activity/multiteamscore.py index 3eac9b7..c053660 100644 --- a/dist/ba_data/python/bastd/activity/multiteamscore.py +++ b/dist/ba_data/python/bastd/activity/multiteamscore.py @@ -28,34 +28,43 @@ class MultiTeamScoreScreenActivity(ScoreScreenActivity): super().on_begin() session = self.session if self._show_up_next and isinstance(session, ba.MultiTeamSession): - txt = ba.Lstr(value='${A} ${B}', - subs=[ - ('${A}', - ba.Lstr(resource='upNextText', - subs=[ - ('${COUNT}', - str(session.get_game_number() + 1)) - ])), - ('${B}', session.get_next_game_description()) - ]) - Text(txt, - maxwidth=900, - h_attach=Text.HAttach.CENTER, - v_attach=Text.VAttach.BOTTOM, - h_align=Text.HAlign.CENTER, - v_align=Text.VAlign.CENTER, - position=(0, 53), - flash=False, - color=(0.3, 0.3, 0.35, 1.0), - transition=Text.Transition.FADE_IN, - transition_delay=2.0).autoretain() + txt = ba.Lstr( + value='${A} ${B}', + subs=[ + ( + '${A}', + ba.Lstr( + resource='upNextText', + subs=[ + ('${COUNT}', str(session.get_game_number() + 1)) + ], + ), + ), + ('${B}', session.get_next_game_description()), + ], + ) + Text( + txt, + maxwidth=900, + h_attach=Text.HAttach.CENTER, + v_attach=Text.VAttach.BOTTOM, + h_align=Text.HAlign.CENTER, + v_align=Text.VAlign.CENTER, + position=(0, 53), + flash=False, + color=(0.3, 0.3, 0.35, 1.0), + transition=Text.Transition.FADE_IN, + transition_delay=2.0, + ).autoretain() - def show_player_scores(self, - delay: float = 2.5, - results: ba.GameResults | None = None, - scale: float = 1.0, - x_offset: float = 0.0, - y_offset: float = 0.0) -> None: + def show_player_scores( + self, + delay: float = 2.5, + results: ba.GameResults | None = None, + scale: float = 1.0, + x_offset: float = 0.0, + y_offset: float = 0.0, + ) -> None: """Show scores for individual players.""" # pylint: disable=too-many-locals # pylint: disable=too-many-statements @@ -96,7 +105,8 @@ class MultiTeamScoreScreenActivity(ScoreScreenActivity): # noinspection PyUnresolvedReferences def _get_player_score_set_entry( - player: ba.SessionPlayer) -> ba.PlayerRecord | None: + player: ba.SessionPlayer, + ) -> ba.PlayerRecord | None: for p_rec in valid_players: if p_rec[1].player is player: return p_rec[1] @@ -108,7 +118,8 @@ class MultiTeamScoreScreenActivity(ScoreScreenActivity): for team in winnergroup.teams: if len(team.players) == 1: player_entry = _get_player_score_set_entry( - team.players[0]) + team.players[0] + ) if player_entry is not None: player_records.append(player_entry) else: @@ -124,33 +135,43 @@ class MultiTeamScoreScreenActivity(ScoreScreenActivity): voffs = -140.0 + spacing * len(player_records) * 0.5 - def _txt(xoffs: float, - yoffs: float, - text: ba.Lstr, - h_align: Text.HAlign = Text.HAlign.RIGHT, - extrascale: float = 1.0, - maxwidth: float | None = 120.0) -> None: - Text(text, - color=(0.5, 0.5, 0.6, 0.5), - position=(ts_h_offs + xoffs * scale, - ts_v_offset + (voffs + yoffs + 4.0) * scale), - h_align=h_align, - v_align=Text.VAlign.CENTER, - scale=0.8 * scale * extrascale, - maxwidth=maxwidth, - transition=Text.Transition.IN_LEFT, - transition_delay=tdelay).autoretain() + def _txt( + xoffs: float, + yoffs: float, + text: ba.Lstr, + h_align: Text.HAlign = Text.HAlign.RIGHT, + extrascale: float = 1.0, + maxwidth: float | None = 120.0, + ) -> None: + Text( + text, + color=(0.5, 0.5, 0.6, 0.5), + position=( + ts_h_offs + xoffs * scale, + ts_v_offset + (voffs + yoffs + 4.0) * scale, + ), + h_align=h_align, + v_align=Text.VAlign.CENTER, + scale=0.8 * scale * extrascale, + maxwidth=maxwidth, + transition=Text.Transition.IN_LEFT, + transition_delay=tdelay, + ).autoretain() session = self.session assert isinstance(session, ba.MultiTeamSession) - tval = ba.Lstr(resource='gameLeadersText', - subs=[('${COUNT}', str(session.get_game_number()))]) - _txt(180, - 43, - tval, - h_align=Text.HAlign.CENTER, - extrascale=1.4, - maxwidth=None) + tval = ba.Lstr( + resource='gameLeadersText', + subs=[('${COUNT}', str(session.get_game_number()))], + ) + _txt( + 180, + 43, + tval, + h_align=Text.HAlign.CENTER, + extrascale=1.4, + maxwidth=None, + ) _txt(-15, 4, ba.Lstr(resource='playerText'), h_align=Text.HAlign.LEFT) _txt(180, 4, ba.Lstr(resource='killsText')) _txt(280, 4, ba.Lstr(resource='deathsText'), maxwidth=100) @@ -162,52 +183,80 @@ class MultiTeamScoreScreenActivity(ScoreScreenActivity): topkillcount = 0 topkilledcount = 99999 - top_score = 0 if not player_records else _get_prec_score( - player_records[0]) + top_score = ( + 0 if not player_records else _get_prec_score(player_records[0]) + ) for prec in player_records: topkillcount = max(topkillcount, prec.accum_kill_count) topkilledcount = min(topkilledcount, prec.accum_killed_count) - def _scoretxt(text: str | ba.Lstr, - x_offs: float, - highlight: bool, - delay2: float, - maxwidth: float = 70.0) -> None: - Text(text, - position=(ts_h_offs + x_offs * scale, - ts_v_offset + (voffs + 15) * scale), - scale=scale, - color=(1.0, 0.9, 0.5, 1.0) if highlight else - (0.5, 0.5, 0.6, 0.5), - h_align=Text.HAlign.RIGHT, - v_align=Text.VAlign.CENTER, - maxwidth=maxwidth, - transition=Text.Transition.IN_LEFT, - transition_delay=tdelay + delay2).autoretain() + def _scoretxt( + text: str | ba.Lstr, + x_offs: float, + highlight: bool, + delay2: float, + maxwidth: float = 70.0, + ) -> None: + Text( + text, + position=( + ts_h_offs + x_offs * scale, + ts_v_offset + (voffs + 15) * scale, + ), + scale=scale, + color=(1.0, 0.9, 0.5, 1.0) + if highlight + else (0.5, 0.5, 0.6, 0.5), + h_align=Text.HAlign.RIGHT, + v_align=Text.VAlign.CENTER, + maxwidth=maxwidth, + transition=Text.Transition.IN_LEFT, + transition_delay=tdelay + delay2, + ).autoretain() for playerrec in player_records: tdelay += 0.05 voffs -= spacing - Image(playerrec.get_icon(), - position=(ts_h_offs - 12 * scale, - ts_v_offset + (voffs + 15.0) * scale), - scale=(30.0 * scale, 30.0 * scale), - transition=Image.Transition.IN_LEFT, - transition_delay=tdelay).autoretain() - Text(ba.Lstr(value=playerrec.getname(full=True)), - maxwidth=160, - scale=0.75 * scale, - position=(ts_h_offs + 10.0 * scale, - ts_v_offset + (voffs + 15) * scale), - h_align=Text.HAlign.LEFT, - v_align=Text.VAlign.CENTER, - color=ba.safecolor(playerrec.team.color + (1, )), - transition=Text.Transition.IN_LEFT, - transition_delay=tdelay).autoretain() - _scoretxt(str(playerrec.accum_kill_count), 180, - playerrec.accum_kill_count == topkillcount, 0.1) - _scoretxt(str(playerrec.accum_killed_count), 280, - playerrec.accum_killed_count == topkilledcount, 0.1) - _scoretxt(_get_prec_score_str(playerrec), 390, - _get_prec_score(playerrec) == top_score, 0.2) + Image( + playerrec.get_icon(), + position=( + ts_h_offs - 12 * scale, + ts_v_offset + (voffs + 15.0) * scale, + ), + scale=(30.0 * scale, 30.0 * scale), + transition=Image.Transition.IN_LEFT, + transition_delay=tdelay, + ).autoretain() + Text( + ba.Lstr(value=playerrec.getname(full=True)), + maxwidth=160, + scale=0.75 * scale, + position=( + ts_h_offs + 10.0 * scale, + ts_v_offset + (voffs + 15) * scale, + ), + h_align=Text.HAlign.LEFT, + v_align=Text.VAlign.CENTER, + color=ba.safecolor(playerrec.team.color + (1,)), + transition=Text.Transition.IN_LEFT, + transition_delay=tdelay, + ).autoretain() + _scoretxt( + str(playerrec.accum_kill_count), + 180, + playerrec.accum_kill_count == topkillcount, + 0.1, + ) + _scoretxt( + str(playerrec.accum_killed_count), + 280, + playerrec.accum_killed_count == topkilledcount, + 0.1, + ) + _scoretxt( + _get_prec_score_str(playerrec), + 390, + _get_prec_score(playerrec) == top_score, + 0.2, + ) diff --git a/dist/ba_data/python/bastd/activity/multiteamvictory.py b/dist/ba_data/python/bastd/activity/multiteamvictory.py index 8c4a60c..5ce4d46 100644 --- a/dist/ba_data/python/bastd/activity/multiteamvictory.py +++ b/dist/ba_data/python/bastd/activity/multiteamvictory.py @@ -33,8 +33,12 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): # pylint: disable=too-many-statements from bastd.actor.text import Text from bastd.actor.image import Image - ba.set_analytics_screen('FreeForAll Series Victory Screen' if self. - _is_ffa else 'Teams Series Victory Screen') + + ba.set_analytics_screen( + 'FreeForAll Series Victory Screen' + if self._is_ffa + else 'Teams Series Victory Screen' + ) if ba.app.ui.uiscale is ba.UIScale.LARGE: sval = ba.Lstr(resource='pressAnyKeyButtonPlayAgainText') else: @@ -46,8 +50,9 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): # Pause a moment before playing victory music. ba.timer(0.6, ba.WeakCall(self._play_victory_music)) - ba.timer(4.4, - ba.WeakCall(self._show_winner, self.settings_raw['winner'])) + ba.timer( + 4.4, ba.WeakCall(self._show_winner, self.settings_raw['winner']) + ) ba.timer(4.6, ba.Call(ba.playsound, self._score_display_sound)) # Score / Name / Player-record. @@ -58,8 +63,12 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): for _pkey, prec in self.stats.get_records().items(): if prec.player.in_game: player_entries.append( - (prec.player.sessionteam.customdata['score'], - prec.getname(full=True), prec)) + ( + prec.player.sessionteam.customdata['score'], + prec.getname(full=True), + prec, + ) + ) player_entries.sort(reverse=True, key=lambda x: x[0]) else: for _pkey, prec in self.stats.get_records().items(): @@ -72,18 +81,29 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): t_incr = 0.12 always_use_first_to = ba.app.lang.get_resource( - 'bestOfUseFirstToInstead') + 'bestOfUseFirstToInstead' + ) session = self.session if self._is_ffa: assert isinstance(session, ba.FreeForAllSession) txt = ba.Lstr( value='${A}:', - subs=[('${A}', - ba.Lstr(resource='firstToFinalText', - subs=[('${COUNT}', - str(session.get_ffa_series_length()))])) - ]) + subs=[ + ( + '${A}', + ba.Lstr( + resource='firstToFinalText', + subs=[ + ( + '${COUNT}', + str(session.get_ffa_series_length()), + ) + ], + ), + ) + ], + ) else: assert isinstance(session, ba.MultiTeamSession) @@ -96,31 +116,52 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): txt = ba.Lstr( value='${A}:', subs=[ - ('${A}', - ba.Lstr(resource='firstToFinalText', - subs=[ - ('${COUNT}', - str(session.get_series_length() / 2 + 1)) - ])) - ]) + ( + '${A}', + ba.Lstr( + resource='firstToFinalText', + subs=[ + ( + '${COUNT}', + str( + session.get_series_length() / 2 + 1 + ), + ) + ], + ), + ) + ], + ) else: txt = ba.Lstr( value='${A}:', - subs=[('${A}', - ba.Lstr(resource='bestOfFinalText', - subs=[('${COUNT}', - str(session.get_series_length()))])) - ]) + subs=[ + ( + '${A}', + ba.Lstr( + resource='bestOfFinalText', + subs=[ + ( + '${COUNT}', + str(session.get_series_length()), + ) + ], + ), + ) + ], + ) - Text(txt, - v_align=Text.VAlign.CENTER, - maxwidth=300, - color=(0.5, 0.5, 0.5, 1.0), - position=(0, 220), - scale=1.2, - transition=Text.Transition.IN_TOP_SLOW, - h_align=Text.HAlign.CENTER, - transition_delay=t_incr * 4).autoretain() + Text( + txt, + v_align=Text.VAlign.CENTER, + maxwidth=300, + color=(0.5, 0.5, 0.5, 1.0), + position=(0, 220), + scale=1.2, + transition=Text.Transition.IN_TOP_SLOW, + h_align=Text.HAlign.CENTER, + transition_delay=t_incr * 4, + ).autoretain() win_score = (session.get_series_length() - 1) // 2 + 1 lose_score = 0 @@ -129,17 +170,23 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): lose_score = team.sessionteam.customdata['score'] if not self._is_ffa: - Text(ba.Lstr(resource='gamesToText', - subs=[('${WINCOUNT}', str(win_score)), - ('${LOSECOUNT}', str(lose_score))]), - color=(0.5, 0.5, 0.5, 1.0), - maxwidth=160, - v_align=Text.VAlign.CENTER, - position=(0, -215), - scale=1.8, - transition=Text.Transition.IN_LEFT, - h_align=Text.HAlign.CENTER, - transition_delay=4.8 + t_incr * 4).autoretain() + Text( + ba.Lstr( + resource='gamesToText', + subs=[ + ('${WINCOUNT}', str(win_score)), + ('${LOSECOUNT}', str(lose_score)), + ], + ), + color=(0.5, 0.5, 0.5, 1.0), + maxwidth=160, + v_align=Text.VAlign.CENTER, + position=(0, -215), + scale=1.8, + transition=Text.Transition.IN_LEFT, + h_align=Text.HAlign.CENTER, + transition_delay=4.8 + t_incr * 4, + ).autoretain() if self._is_ffa: v_extra = 120 @@ -158,31 +205,37 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): mvp_name = entry[1] break if mvp is not None: - Text(ba.Lstr(resource='mostValuablePlayerText'), - color=(0.5, 0.5, 0.5, 1.0), - v_align=Text.VAlign.CENTER, - maxwidth=300, - position=(180, ts_height / 2 + 15), - transition=Text.Transition.IN_LEFT, - h_align=Text.HAlign.LEFT, - transition_delay=tval).autoretain() + Text( + ba.Lstr(resource='mostValuablePlayerText'), + color=(0.5, 0.5, 0.5, 1.0), + v_align=Text.VAlign.CENTER, + maxwidth=300, + position=(180, ts_height / 2 + 15), + transition=Text.Transition.IN_LEFT, + h_align=Text.HAlign.LEFT, + transition_delay=tval, + ).autoretain() tval += 4 * t_incr - Image(mvp.get_icon(), - position=(230, ts_height / 2 - 55 + 14 - 5), - scale=(70, 70), - transition=Image.Transition.IN_LEFT, - transition_delay=tval).autoretain() + Image( + mvp.get_icon(), + position=(230, ts_height / 2 - 55 + 14 - 5), + scale=(70, 70), + transition=Image.Transition.IN_LEFT, + transition_delay=tval, + ).autoretain() assert mvp_name is not None - Text(ba.Lstr(value=mvp_name), - position=(280, ts_height / 2 - 55 + 15 - 5), - h_align=Text.HAlign.LEFT, - v_align=Text.VAlign.CENTER, - maxwidth=170, - scale=1.3, - color=ba.safecolor(mvp.team.color + (1, )), - transition=Text.Transition.IN_LEFT, - transition_delay=tval).autoretain() + Text( + ba.Lstr(value=mvp_name), + position=(280, ts_height / 2 - 55 + 15 - 5), + h_align=Text.HAlign.LEFT, + v_align=Text.VAlign.CENTER, + maxwidth=170, + scale=1.3, + color=ba.safecolor(mvp.team.color + (1,)), + transition=Text.Transition.IN_LEFT, + transition_delay=tval, + ).autoretain() tval += 4 * t_incr # Most violent. @@ -193,41 +246,56 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): mvp_name = entry[1] most_kills = entry[2].kill_count if mvp is not None: - Text(ba.Lstr(resource='mostViolentPlayerText'), - color=(0.5, 0.5, 0.5, 1.0), - v_align=Text.VAlign.CENTER, - maxwidth=300, - position=(180, ts_height / 2 - 150 + v_extra + 15), - transition=Text.Transition.IN_LEFT, - h_align=Text.HAlign.LEFT, - transition_delay=tval).autoretain() - Text(ba.Lstr(value='(${A})', - subs=[('${A}', - ba.Lstr(resource='killsTallyText', - subs=[('${COUNT}', str(most_kills))])) - ]), - position=(260, ts_height / 2 - 150 - 15 + v_extra), - color=(0.3, 0.3, 0.3, 1.0), - scale=0.6, - h_align=Text.HAlign.LEFT, - transition=Text.Transition.IN_LEFT, - transition_delay=tval).autoretain() + Text( + ba.Lstr(resource='mostViolentPlayerText'), + color=(0.5, 0.5, 0.5, 1.0), + v_align=Text.VAlign.CENTER, + maxwidth=300, + position=(180, ts_height / 2 - 150 + v_extra + 15), + transition=Text.Transition.IN_LEFT, + h_align=Text.HAlign.LEFT, + transition_delay=tval, + ).autoretain() + Text( + ba.Lstr( + value='(${A})', + subs=[ + ( + '${A}', + ba.Lstr( + resource='killsTallyText', + subs=[('${COUNT}', str(most_kills))], + ), + ) + ], + ), + position=(260, ts_height / 2 - 150 - 15 + v_extra), + color=(0.3, 0.3, 0.3, 1.0), + scale=0.6, + h_align=Text.HAlign.LEFT, + transition=Text.Transition.IN_LEFT, + transition_delay=tval, + ).autoretain() tval += 4 * t_incr - Image(mvp.get_icon(), - position=(233, ts_height / 2 - 150 - 30 - 46 + 25 + v_extra), - scale=(50, 50), - transition=Image.Transition.IN_LEFT, - transition_delay=tval).autoretain() + Image( + mvp.get_icon(), + position=(233, ts_height / 2 - 150 - 30 - 46 + 25 + v_extra), + scale=(50, 50), + transition=Image.Transition.IN_LEFT, + transition_delay=tval, + ).autoretain() assert mvp_name is not None - Text(ba.Lstr(value=mvp_name), - position=(270, ts_height / 2 - 150 - 30 - 36 + v_extra + 15), - h_align=Text.HAlign.LEFT, - v_align=Text.VAlign.CENTER, - maxwidth=180, - color=ba.safecolor(mvp.team.color + (1, )), - transition=Text.Transition.IN_LEFT, - transition_delay=tval).autoretain() + Text( + ba.Lstr(value=mvp_name), + position=(270, ts_height / 2 - 150 - 30 - 36 + v_extra + 15), + h_align=Text.HAlign.LEFT, + v_align=Text.VAlign.CENTER, + maxwidth=180, + color=ba.safecolor(mvp.team.color + (1,)), + transition=Text.Transition.IN_LEFT, + transition_delay=tval, + ).autoretain() tval += 4 * t_incr # Most killed. @@ -239,49 +307,66 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): mkp_name = entry[1] most_killed = entry[2].killed_count if mkp is not None: - Text(ba.Lstr(resource='mostViolatedPlayerText'), - color=(0.5, 0.5, 0.5, 1.0), - v_align=Text.VAlign.CENTER, - maxwidth=300, - position=(180, ts_height / 2 - 300 + v_extra + 15), - transition=Text.Transition.IN_LEFT, - h_align=Text.HAlign.LEFT, - transition_delay=tval).autoretain() - Text(ba.Lstr(value='(${A})', - subs=[('${A}', - ba.Lstr(resource='deathsTallyText', - subs=[('${COUNT}', str(most_killed))])) - ]), - position=(260, ts_height / 2 - 300 - 15 + v_extra), - h_align=Text.HAlign.LEFT, - scale=0.6, - color=(0.3, 0.3, 0.3, 1.0), - transition=Text.Transition.IN_LEFT, - transition_delay=tval).autoretain() + Text( + ba.Lstr(resource='mostViolatedPlayerText'), + color=(0.5, 0.5, 0.5, 1.0), + v_align=Text.VAlign.CENTER, + maxwidth=300, + position=(180, ts_height / 2 - 300 + v_extra + 15), + transition=Text.Transition.IN_LEFT, + h_align=Text.HAlign.LEFT, + transition_delay=tval, + ).autoretain() + Text( + ba.Lstr( + value='(${A})', + subs=[ + ( + '${A}', + ba.Lstr( + resource='deathsTallyText', + subs=[('${COUNT}', str(most_killed))], + ), + ) + ], + ), + position=(260, ts_height / 2 - 300 - 15 + v_extra), + h_align=Text.HAlign.LEFT, + scale=0.6, + color=(0.3, 0.3, 0.3, 1.0), + transition=Text.Transition.IN_LEFT, + transition_delay=tval, + ).autoretain() tval += 4 * t_incr - Image(mkp.get_icon(), - position=(233, ts_height / 2 - 300 - 30 - 46 + 25 + v_extra), - scale=(50, 50), - transition=Image.Transition.IN_LEFT, - transition_delay=tval).autoretain() + Image( + mkp.get_icon(), + position=(233, ts_height / 2 - 300 - 30 - 46 + 25 + v_extra), + scale=(50, 50), + transition=Image.Transition.IN_LEFT, + transition_delay=tval, + ).autoretain() assert mkp_name is not None - Text(ba.Lstr(value=mkp_name), - position=(270, ts_height / 2 - 300 - 30 - 36 + v_extra + 15), - h_align=Text.HAlign.LEFT, - v_align=Text.VAlign.CENTER, - color=ba.safecolor(mkp.team.color + (1, )), - maxwidth=180, - transition=Text.Transition.IN_LEFT, - transition_delay=tval).autoretain() + Text( + ba.Lstr(value=mkp_name), + position=(270, ts_height / 2 - 300 - 30 - 36 + v_extra + 15), + h_align=Text.HAlign.LEFT, + v_align=Text.VAlign.CENTER, + color=ba.safecolor(mkp.team.color + (1,)), + maxwidth=180, + transition=Text.Transition.IN_LEFT, + transition_delay=tval, + ).autoretain() tval += 4 * t_incr # Now show individual scores. tdelay = tval - Text(ba.Lstr(resource='finalScoresText'), - color=(0.5, 0.5, 0.5, 1.0), - position=(ts_h_offs, ts_height / 2), - transition=Text.Transition.IN_RIGHT, - transition_delay=tdelay).autoretain() + Text( + ba.Lstr(resource='finalScoresText'), + color=(0.5, 0.5, 0.5, 1.0), + position=(ts_h_offs, ts_height / 2), + transition=Text.Transition.IN_RIGHT, + transition_delay=tdelay, + ).autoretain() tdelay += 4 * t_incr v_offs = 0.0 @@ -289,33 +374,41 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): for _score, name, prec in player_entries: tdelay -= 4 * t_incr v_offs -= 40 - Text(str(prec.team.customdata['score']) - if self._is_ffa else str(prec.score), - color=(0.5, 0.5, 0.5, 1.0), - position=(ts_h_offs + 230, ts_height / 2 + v_offs), - h_align=Text.HAlign.RIGHT, - transition=Text.Transition.IN_RIGHT, - transition_delay=tdelay).autoretain() + Text( + str(prec.team.customdata['score']) + if self._is_ffa + else str(prec.score), + color=(0.5, 0.5, 0.5, 1.0), + position=(ts_h_offs + 230, ts_height / 2 + v_offs), + h_align=Text.HAlign.RIGHT, + transition=Text.Transition.IN_RIGHT, + transition_delay=tdelay, + ).autoretain() tdelay -= 4 * t_incr - Image(prec.get_icon(), - position=(ts_h_offs - 72, ts_height / 2 + v_offs + 15), - scale=(30, 30), - transition=Image.Transition.IN_LEFT, - transition_delay=tdelay).autoretain() - Text(ba.Lstr(value=name), - position=(ts_h_offs - 50, ts_height / 2 + v_offs + 15), - h_align=Text.HAlign.LEFT, - v_align=Text.VAlign.CENTER, - maxwidth=180, - color=ba.safecolor(prec.team.color + (1, )), - transition=Text.Transition.IN_RIGHT, - transition_delay=tdelay).autoretain() + Image( + prec.get_icon(), + position=(ts_h_offs - 72, ts_height / 2 + v_offs + 15), + scale=(30, 30), + transition=Image.Transition.IN_LEFT, + transition_delay=tdelay, + ).autoretain() + Text( + ba.Lstr(value=name), + position=(ts_h_offs - 50, ts_height / 2 + v_offs + 15), + h_align=Text.HAlign.LEFT, + v_align=Text.VAlign.CENTER, + maxwidth=180, + color=ba.safecolor(prec.team.color + (1,)), + transition=Text.Transition.IN_RIGHT, + transition_delay=tdelay, + ).autoretain() ba.timer(15.0, ba.WeakCall(self._show_tips)) def _show_tips(self) -> None: from bastd.actor.tipstext import TipsText + self._tips_text = TipsText(offs_y=70) def _play_victory_music(self) -> None: @@ -327,29 +420,37 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): def _show_winner(self, team: ba.SessionTeam) -> None: from bastd.actor.image import Image from bastd.actor.zoomtext import ZoomText + if not self._is_ffa: offs_v = 0.0 - ZoomText(team.name, - position=(0, 97), - color=team.color, - scale=1.15, - jitter=1.0, - maxwidth=250).autoretain() + ZoomText( + team.name, + position=(0, 97), + color=team.color, + scale=1.15, + jitter=1.0, + maxwidth=250, + ).autoretain() else: offs_v = -80.0 if len(team.players) == 1: - i = Image(team.players[0].get_icon(), - position=(0, 143), - scale=(100, 100)).autoretain() + i = Image( + team.players[0].get_icon(), + position=(0, 143), + scale=(100, 100), + ).autoretain() assert i.node ba.animate(i.node, 'opacity', {0.0: 0.0, 0.25: 1.0}) - ZoomText(ba.Lstr( - value=team.players[0].getname(full=True, icon=False)), - position=(0, 97 + offs_v), - color=team.color, - scale=1.15, - jitter=1.0, - maxwidth=250).autoretain() + ZoomText( + ba.Lstr( + value=team.players[0].getname(full=True, icon=False) + ), + position=(0, 97 + offs_v), + color=team.color, + scale=1.15, + jitter=1.0, + maxwidth=250, + ).autoretain() s_extra = 1.0 if self._is_ffa else 1.0 @@ -362,15 +463,19 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): # Temp - if these come up as the english default, fall-back to the # unified old form which is more likely to be translated. - ZoomText(wins_text, - position=(0, -10 + offs_v), - color=team.color, - scale=0.65 * s_extra, - jitter=1.0, - maxwidth=250).autoretain() - ZoomText(ba.Lstr(resource='seriesWinLine2Text'), - position=(0, -110 + offs_v), - scale=1.0 * s_extra, - color=team.color, - jitter=1.0, - maxwidth=250).autoretain() + ZoomText( + wins_text, + position=(0, -10 + offs_v), + color=team.color, + scale=0.65 * s_extra, + jitter=1.0, + maxwidth=250, + ).autoretain() + ZoomText( + ba.Lstr(resource='seriesWinLine2Text'), + position=(0, -110 + offs_v), + scale=1.0 * s_extra, + color=team.color, + jitter=1.0, + maxwidth=250, + ).autoretain() diff --git a/dist/ba_data/python/bastd/actor/background.py b/dist/ba_data/python/bastd/actor/background.py index f9c486b..4c99717 100644 --- a/dist/ba_data/python/bastd/actor/background.py +++ b/dist/ba_data/python/bastd/actor/background.py @@ -17,10 +17,12 @@ if TYPE_CHECKING: class Background(ba.Actor): """Simple Fading Background Actor.""" - def __init__(self, - fade_time: float = 0.5, - start_faded: bool = False, - show_logo: bool = False): + def __init__( + self, + fade_time: float = 0.5, + start_faded: bool = False, + show_logo: bool = False, + ): super().__init__() self._dying = False self.fade_time = fade_time @@ -31,22 +33,24 @@ class Background(ba.Actor): session = ba.getsession() self._session = weakref.ref(session) with ba.Context(session): - self.node = ba.newnode('image', - delegate=self, - attrs={ - 'fill_screen': True, - 'texture': ba.gettexture('bg'), - 'tilt_translate': -0.3, - 'has_alpha_channel': False, - 'color': (1, 1, 1) - }) + self.node = ba.newnode( + 'image', + delegate=self, + attrs={ + 'fill_screen': True, + 'texture': ba.gettexture('bg'), + 'tilt_translate': -0.3, + 'has_alpha_channel': False, + 'color': (1, 1, 1), + }, + ) if not start_faded: - ba.animate(self.node, - 'opacity', { - 0.0: 0.0, - self.fade_time: 1.0 - }, - loop=False) + ba.animate( + self.node, + 'opacity', + {0.0: 0.0, self.fade_time: 1.0}, + loop=False, + ) if show_logo: logo_texture = ba.gettexture('logo') logo_model = ba.getmodel('logo') @@ -63,27 +67,27 @@ class Background(ba.Actor): 'color': (0.15, 0.15, 0.15), 'position': (0, 0), 'tilt_translate': -0.05, - 'absolute_scale': False - }) + 'absolute_scale': False, + }, + ) self.node.connectattr('opacity', self.logo, 'opacity') # add jitter/pulse for a stop-motion-y look unless we're in VR # in which case stillness is better if not ba.app.vr_mode: - self.cmb = ba.newnode('combine', - owner=self.node, - attrs={'size': 2}) + self.cmb = ba.newnode( + 'combine', owner=self.node, attrs={'size': 2} + ) for attr in ['input0', 'input1']: - ba.animate(self.cmb, - attr, { - 0.0: 0.693, - 0.05: 0.7, - 0.5: 0.693 - }, - loop=True) + ba.animate( + self.cmb, + attr, + {0.0: 0.693, 0.05: 0.7, 0.5: 0.693}, + loop=True, + ) self.cmb.connectattr('output', self.logo, 'scale') - cmb = ba.newnode('combine', - owner=self.node, - attrs={'size': 2}) + cmb = ba.newnode( + 'combine', owner=self.node, attrs={'size': 2} + ) cmb.connectattr('output', self.logo, 'position') # Gen some random keys for that stop-motion-y look. keys = {} @@ -114,8 +118,10 @@ class Background(ba.Actor): # since it was part of the session's scene. # Let's make sure that's the case. # (since otherwise we have no way to kill it) - ba.print_error('got None session on Background _die' - ' (and node still exists!)') + ba.print_error( + 'got None session on Background _die' + ' (and node still exists!)' + ) elif session is not None: with ba.Context(session): if not self._dying and self.node: @@ -123,12 +129,12 @@ class Background(ba.Actor): if immediate: self.node.delete() else: - ba.animate(self.node, - 'opacity', { - 0.0: 1.0, - self.fade_time: 0.0 - }, - loop=False) + ba.animate( + self.node, + 'opacity', + {0.0: 1.0, self.fade_time: 0.0}, + loop=False, + ) ba.timer(self.fade_time + 0.1, self.node.delete) def handlemessage(self, msg: Any) -> Any: diff --git a/dist/ba_data/python/bastd/actor/bomb.py b/dist/ba_data/python/bastd/actor/bomb.py index 626a25a..43becf1 100644 --- a/dist/ba_data/python/bastd/actor/bomb.py +++ b/dist/ba_data/python/bastd/actor/bomb.py @@ -172,11 +172,13 @@ class BombFactory: self.debris_fall_sound = ba.getsound('debrisFall') self.wood_debris_fall_sound = ba.getsound('woodDebrisFall') - self.explode_sounds = (ba.getsound('explosion01'), - ba.getsound('explosion02'), - ba.getsound('explosion03'), - ba.getsound('explosion04'), - ba.getsound('explosion05')) + self.explode_sounds = ( + ba.getsound('explosion01'), + ba.getsound('explosion02'), + ba.getsound('explosion03'), + ba.getsound('explosion04'), + ba.getsound('explosion05'), + ) self.freeze_sound = ba.getsound('freeze') self.fuse_sound = ba.getsound('fuse01') @@ -209,8 +211,9 @@ class BombFactory: actions=('modify_part_collision', 'use_node_collide', False), ) - self.bomb_material.add_actions(actions=('modify_part_collision', - 'friction', 0.3)) + self.bomb_material.add_actions( + actions=('modify_part_collision', 'friction', 0.3) + ) self.land_mine_no_explode_material = ba.Material() self.land_mine_blast_material = ba.Material() @@ -220,11 +223,13 @@ class BombFactory: 'and', ('they_are_older_than', 200), 'and', - ('eval_colliding', ), + ('eval_colliding',), 'and', ( - ('they_dont_have_material', - self.land_mine_no_explode_material), + ( + 'they_dont_have_material', + self.land_mine_no_explode_material, + ), 'and', ( ('they_have_material', shared.object_material), @@ -243,7 +248,7 @@ class BombFactory: 'and', ('they_are_older_than', 200), 'and', - ('eval_colliding', ), + ('eval_colliding',), 'and', ( ('they_have_material', shared.footing_material), @@ -264,8 +269,10 @@ class BombFactory: ), ) - self.dink_sounds = (ba.getsound('bombDrop01'), - ba.getsound('bombDrop02')) + self.dink_sounds = ( + ba.getsound('bombDrop01'), + ba.getsound('bombDrop02'), + ) self.sticky_impact_sound = ba.getsound('stickyImpact') self.roll_sound = ba.getsound('bombRoll01') @@ -275,12 +282,15 @@ class BombFactory: actions=( ('impact_sound', self.dink_sounds, 2, 0.8), ('roll_sound', self.roll_sound, 3, 6), - )) + ), + ) - self.sticky_material.add_actions(actions=(('modify_part_collision', - 'stiffness', 0.1), - ('modify_part_collision', - 'damping', 1.0))) + self.sticky_material.add_actions( + actions=( + ('modify_part_collision', 'stiffness', 0.1), + ('modify_part_collision', 'damping', 1.0), + ) + ) self.sticky_material.add_actions( conditions=( @@ -322,14 +332,16 @@ class Blast(ba.Actor): category: Gameplay Classes """ - def __init__(self, - position: Sequence[float] = (0.0, 1.0, 0.0), - velocity: Sequence[float] = (0.0, 0.0, 0.0), - blast_radius: float = 2.0, - blast_type: str = 'normal', - source_player: ba.Player | None = None, - hit_type: str = 'explosion', - hit_subtype: str = 'normal'): + def __init__( + self, + position: Sequence[float] = (0.0, 1.0, 0.0), + velocity: Sequence[float] = (0.0, 0.0, 0.0), + blast_radius: float = 2.0, + blast_type: str = 'normal', + source_player: ba.Player | None = None, + hit_type: str = 'explosion', + hit_subtype: str = 'normal', + ): """Instantiate with given values.""" # bah; get off my lawn! @@ -356,7 +368,7 @@ class Blast(ba.Actor): 'position': (position[0], position[1] - 0.1, position[2]), 'scale': (self.radius, self.radius, self.radius), 'type': 'sphere', - 'materials': rmats + 'materials': rmats, }, ) @@ -364,44 +376,54 @@ class Blast(ba.Actor): # Throw in an explosion and flash. evel = (velocity[0], max(-1.0, velocity[1]), velocity[2]) - explosion = ba.newnode('explosion', - attrs={ - 'position': position, - 'velocity': evel, - 'radius': self.radius, - 'big': (self.blast_type == 'tnt') - }) + explosion = ba.newnode( + 'explosion', + attrs={ + 'position': position, + 'velocity': evel, + 'radius': self.radius, + 'big': (self.blast_type == 'tnt'), + }, + ) if self.blast_type == 'ice': explosion.color = (0, 0.05, 0.4) ba.timer(1.0, explosion.delete) if self.blast_type != 'ice': - ba.emitfx(position=position, - velocity=velocity, - count=int(1.0 + random.random() * 4), - emit_type='tendrils', - tendril_type='thin_smoke') - ba.emitfx(position=position, - velocity=velocity, - count=int(4.0 + random.random() * 4), - emit_type='tendrils', - tendril_type='ice' if self.blast_type == 'ice' else 'smoke') - ba.emitfx(position=position, - emit_type='distortion', - spread=1.0 if self.blast_type == 'tnt' else 2.0) + ba.emitfx( + position=position, + velocity=velocity, + count=int(1.0 + random.random() * 4), + emit_type='tendrils', + tendril_type='thin_smoke', + ) + ba.emitfx( + position=position, + velocity=velocity, + count=int(4.0 + random.random() * 4), + emit_type='tendrils', + tendril_type='ice' if self.blast_type == 'ice' else 'smoke', + ) + ba.emitfx( + position=position, + emit_type='distortion', + spread=1.0 if self.blast_type == 'tnt' else 2.0, + ) # And emit some shrapnel. if self.blast_type == 'ice': def emit() -> None: - ba.emitfx(position=position, - velocity=velocity, - count=30, - spread=2.0, - scale=0.4, - chunk_type='ice', - emit_type='stickers') + ba.emitfx( + position=position, + velocity=velocity, + count=30, + spread=2.0, + scale=0.4, + chunk_type='ice', + emit_type='stickers', + ) # It looks better if we delay a bit. ba.timer(0.05, emit) @@ -409,35 +431,45 @@ class Blast(ba.Actor): elif self.blast_type == 'sticky': def emit() -> None: - ba.emitfx(position=position, - velocity=velocity, - count=int(4.0 + random.random() * 8), - spread=0.7, - chunk_type='slime') - ba.emitfx(position=position, - velocity=velocity, - count=int(4.0 + random.random() * 8), - scale=0.5, - spread=0.7, - chunk_type='slime') - ba.emitfx(position=position, - velocity=velocity, - count=15, - scale=0.6, - chunk_type='slime', - emit_type='stickers') - ba.emitfx(position=position, - velocity=velocity, - count=20, - scale=0.7, - chunk_type='spark', - emit_type='stickers') - ba.emitfx(position=position, - velocity=velocity, - count=int(6.0 + random.random() * 12), - scale=0.8, - spread=1.5, - chunk_type='spark') + ba.emitfx( + position=position, + velocity=velocity, + count=int(4.0 + random.random() * 8), + spread=0.7, + chunk_type='slime', + ) + ba.emitfx( + position=position, + velocity=velocity, + count=int(4.0 + random.random() * 8), + scale=0.5, + spread=0.7, + chunk_type='slime', + ) + ba.emitfx( + position=position, + velocity=velocity, + count=15, + scale=0.6, + chunk_type='slime', + emit_type='stickers', + ) + ba.emitfx( + position=position, + velocity=velocity, + count=20, + scale=0.7, + chunk_type='spark', + emit_type='stickers', + ) + ba.emitfx( + position=position, + velocity=velocity, + count=int(6.0 + random.random() * 12), + scale=0.8, + spread=1.5, + chunk_type='spark', + ) # It looks better if we delay a bit. ba.timer(0.05, emit) @@ -445,28 +477,36 @@ class Blast(ba.Actor): elif self.blast_type == 'impact': def emit() -> None: - ba.emitfx(position=position, - velocity=velocity, - count=int(4.0 + random.random() * 8), - scale=0.8, - chunk_type='metal') - ba.emitfx(position=position, - velocity=velocity, - count=int(4.0 + random.random() * 8), - scale=0.4, - chunk_type='metal') - ba.emitfx(position=position, - velocity=velocity, - count=20, - scale=0.7, - chunk_type='spark', - emit_type='stickers') - ba.emitfx(position=position, - velocity=velocity, - count=int(8.0 + random.random() * 15), - scale=0.8, - spread=1.5, - chunk_type='spark') + ba.emitfx( + position=position, + velocity=velocity, + count=int(4.0 + random.random() * 8), + scale=0.8, + chunk_type='metal', + ) + ba.emitfx( + position=position, + velocity=velocity, + count=int(4.0 + random.random() * 8), + scale=0.4, + chunk_type='metal', + ) + ba.emitfx( + position=position, + velocity=velocity, + count=20, + scale=0.7, + chunk_type='spark', + emit_type='stickers', + ) + ba.emitfx( + position=position, + velocity=velocity, + count=int(8.0 + random.random() * 15), + scale=0.8, + spread=1.5, + chunk_type='spark', + ) # It looks better if we delay a bit. ba.timer(0.05, emit) @@ -475,38 +515,48 @@ class Blast(ba.Actor): def emit() -> None: if self.blast_type != 'tnt': - ba.emitfx(position=position, - velocity=velocity, - count=int(4.0 + random.random() * 8), - chunk_type='rock') - ba.emitfx(position=position, - velocity=velocity, - count=int(4.0 + random.random() * 8), - scale=0.5, - chunk_type='rock') - ba.emitfx(position=position, - velocity=velocity, - count=30, - scale=1.0 if self.blast_type == 'tnt' else 0.7, - chunk_type='spark', - emit_type='stickers') - ba.emitfx(position=position, - velocity=velocity, - count=int(18.0 + random.random() * 20), - scale=1.0 if self.blast_type == 'tnt' else 0.8, - spread=1.5, - chunk_type='spark') + ba.emitfx( + position=position, + velocity=velocity, + count=int(4.0 + random.random() * 8), + chunk_type='rock', + ) + ba.emitfx( + position=position, + velocity=velocity, + count=int(4.0 + random.random() * 8), + scale=0.5, + chunk_type='rock', + ) + ba.emitfx( + position=position, + velocity=velocity, + count=30, + scale=1.0 if self.blast_type == 'tnt' else 0.7, + chunk_type='spark', + emit_type='stickers', + ) + ba.emitfx( + position=position, + velocity=velocity, + count=int(18.0 + random.random() * 20), + scale=1.0 if self.blast_type == 'tnt' else 0.8, + spread=1.5, + chunk_type='spark', + ) # TNT throws splintery chunks. if self.blast_type == 'tnt': def emit_splinters() -> None: - ba.emitfx(position=position, - velocity=velocity, - count=int(20.0 + random.random() * 25), - scale=0.8, - spread=1.0, - chunk_type='splinter') + ba.emitfx( + position=position, + velocity=velocity, + count=int(20.0 + random.random() * 25), + scale=0.8, + spread=1.0, + chunk_type='splinter', + ) ba.timer(0.01, emit_splinters) @@ -514,26 +564,29 @@ class Blast(ba.Actor): if self.blast_type == 'tnt' or random.random() < 0.1: def emit_extra_sparks() -> None: - ba.emitfx(position=position, - velocity=velocity, - count=int(10.0 + random.random() * 20), - scale=0.8, - spread=1.5, - chunk_type='spark') + ba.emitfx( + position=position, + velocity=velocity, + count=int(10.0 + random.random() * 20), + scale=0.8, + spread=1.5, + chunk_type='spark', + ) ba.timer(0.02, emit_extra_sparks) # It looks better if we delay a bit. ba.timer(0.05, emit) - lcolor = ((0.6, 0.6, 1.0) if self.blast_type == 'ice' else - (1, 0.3, 0.1)) - light = ba.newnode('light', - attrs={ - 'position': position, - 'volume_intensity_scale': 10.0, - 'color': lcolor - }) + lcolor = (0.6, 0.6, 1.0) if self.blast_type == 'ice' else (1, 0.3, 0.1) + light = ba.newnode( + 'light', + attrs={ + 'position': position, + 'volume_intensity_scale': 10.0, + 'color': lcolor, + }, + ) scl = random.uniform(0.6, 0.9) scorch_radius = light_radius = self.radius @@ -544,7 +597,9 @@ class Blast(ba.Actor): iscale = 1.6 ba.animate( - light, 'intensity', { + light, + 'intensity', + { 0: 2.0 * iscale, scl * 0.02: 0.1 * iscale, scl * 0.025: 0.2 * iscale, @@ -553,25 +608,31 @@ class Blast(ba.Actor): scl * 0.08: 4.0 * iscale, scl * 0.2: 0.6 * iscale, scl * 2.0: 0.00 * iscale, - scl * 3.0: 0.0 - }) + scl * 3.0: 0.0, + }, + ) ba.animate( - light, 'radius', { + light, + 'radius', + { 0: light_radius * 0.2, scl * 0.05: light_radius * 0.55, scl * 0.1: light_radius * 0.3, scl * 0.3: light_radius * 0.15, - scl * 1.0: light_radius * 0.05 - }) + scl * 1.0: light_radius * 0.05, + }, + ) ba.timer(scl * 3.0, light.delete) # Make a scorch that fades over time. - scorch = ba.newnode('scorch', - attrs={ - 'position': position, - 'size': scorch_radius * 0.5, - 'big': (self.blast_type == 'tnt') - }) + scorch = ba.newnode( + 'scorch', + attrs={ + 'position': position, + 'size': scorch_radius * 0.5, + 'big': (self.blast_type == 'tnt'), + }, + ) if self.blast_type == 'ice': scorch.color = (1, 1, 1.5) @@ -622,17 +683,20 @@ class Blast(ba.Actor): mag *= 2.0 node.handlemessage( - ba.HitMessage(pos=nodepos, - velocity=(0, 0, 0), - magnitude=mag, - hit_type=self.hit_type, - hit_subtype=self.hit_subtype, - radius=self.radius, - source_player=ba.existing(self._source_player))) + ba.HitMessage( + pos=nodepos, + velocity=(0, 0, 0), + magnitude=mag, + hit_type=self.hit_type, + hit_subtype=self.hit_subtype, + radius=self.radius, + source_player=ba.existing(self._source_player), + ) + ) if self.blast_type == 'ice': - ba.playsound(BombFactory.get().freeze_sound, - 10, - position=nodepos) + ba.playsound( + BombFactory.get().freeze_sound, 10, position=nodepos + ) node.handlemessage(ba.FreezeMessage()) else: @@ -651,14 +715,16 @@ class Bomb(ba.Actor): # pylint: disable=too-many-branches # pylint: disable=too-many-statements - def __init__(self, - position: Sequence[float] = (0.0, 1.0, 0.0), - velocity: Sequence[float] = (0.0, 0.0, 0.0), - bomb_type: str = 'normal', - blast_radius: float = 2.0, - bomb_scale: float = 1.0, - source_player: ba.Player | None = None, - owner: ba.Node | None = None): + def __init__( + self, + position: Sequence[float] = (0.0, 1.0, 0.0), + velocity: Sequence[float] = (0.0, 0.0, 0.0), + bomb_type: str = 'normal', + blast_radius: float = 2.0, + bomb_scale: float = 1.0, + source_player: ba.Player | None = None, + owner: ba.Node | None = None, + ): """Create a new Bomb. bomb_type can be 'ice','impact','land_mine','normal','sticky', or @@ -670,8 +736,14 @@ class Bomb(ba.Actor): shared = SharedObjects.get() factory = BombFactory.get() - if bomb_type not in ('ice', 'impact', 'land_mine', 'normal', 'sticky', - 'tnt'): + if bomb_type not in ( + 'ice', + 'impact', + 'land_mine', + 'normal', + 'sticky', + 'tnt', + ): raise ValueError('invalid bomb type: ' + bomb_type) self.bomb_type = bomb_type @@ -718,78 +790,88 @@ class Bomb(ba.Actor): # wanna add this even in the tnt case? materials: tuple[ba.Material, ...] if self.bomb_type == 'tnt': - materials = (factory.bomb_material, shared.footing_material, - shared.object_material) + materials = ( + factory.bomb_material, + shared.footing_material, + shared.object_material, + ) else: materials = (factory.bomb_material, shared.object_material) if self.bomb_type == 'impact': - materials = materials + (factory.impact_blast_material, ) + materials = materials + (factory.impact_blast_material,) elif self.bomb_type == 'land_mine': - materials = materials + (factory.land_mine_no_explode_material, ) + materials = materials + (factory.land_mine_no_explode_material,) if self.bomb_type == 'sticky': - materials = materials + (factory.sticky_material, ) + materials = materials + (factory.sticky_material,) else: - materials = materials + (factory.normal_sound_material, ) + materials = materials + (factory.normal_sound_material,) if self.bomb_type == 'land_mine': fuse_time = None - self.node = ba.newnode('prop', - delegate=self, - attrs={ - 'position': position, - 'velocity': velocity, - 'model': factory.land_mine_model, - 'light_model': factory.land_mine_model, - 'body': 'landMine', - 'body_scale': self.scale, - 'shadow_size': 0.44, - 'color_texture': factory.land_mine_tex, - 'reflection': 'powerup', - 'reflection_scale': [1.0], - 'materials': materials - }) + self.node = ba.newnode( + 'prop', + delegate=self, + attrs={ + 'position': position, + 'velocity': velocity, + 'model': factory.land_mine_model, + 'light_model': factory.land_mine_model, + 'body': 'landMine', + 'body_scale': self.scale, + 'shadow_size': 0.44, + 'color_texture': factory.land_mine_tex, + 'reflection': 'powerup', + 'reflection_scale': [1.0], + 'materials': materials, + }, + ) elif self.bomb_type == 'tnt': fuse_time = None - self.node = ba.newnode('prop', - delegate=self, - attrs={ - 'position': position, - 'velocity': velocity, - 'model': factory.tnt_model, - 'light_model': factory.tnt_model, - 'body': 'crate', - 'body_scale': self.scale, - 'shadow_size': 0.5, - 'color_texture': factory.tnt_tex, - 'reflection': 'soft', - 'reflection_scale': [0.23], - 'materials': materials - }) + self.node = ba.newnode( + 'prop', + delegate=self, + attrs={ + 'position': position, + 'velocity': velocity, + 'model': factory.tnt_model, + 'light_model': factory.tnt_model, + 'body': 'crate', + 'body_scale': self.scale, + 'shadow_size': 0.5, + 'color_texture': factory.tnt_tex, + 'reflection': 'soft', + 'reflection_scale': [0.23], + 'materials': materials, + }, + ) elif self.bomb_type == 'impact': fuse_time = 20.0 - self.node = ba.newnode('prop', - delegate=self, - attrs={ - 'position': position, - 'velocity': velocity, - 'body': 'sphere', - 'body_scale': self.scale, - 'model': factory.impact_bomb_model, - 'shadow_size': 0.3, - 'color_texture': factory.impact_tex, - 'reflection': 'powerup', - 'reflection_scale': [1.5], - 'materials': materials - }) + self.node = ba.newnode( + 'prop', + delegate=self, + attrs={ + 'position': position, + 'velocity': velocity, + 'body': 'sphere', + 'body_scale': self.scale, + 'model': factory.impact_bomb_model, + 'shadow_size': 0.3, + 'color_texture': factory.impact_tex, + 'reflection': 'powerup', + 'reflection_scale': [1.5], + 'materials': materials, + }, + ) self.arm_timer = ba.Timer( - 0.2, ba.WeakCall(self.handlemessage, ArmMessage())) + 0.2, ba.WeakCall(self.handlemessage, ArmMessage()) + ) self.warn_timer = ba.Timer( - fuse_time - 1.7, ba.WeakCall(self.handlemessage, - WarnMessage())) + fuse_time - 1.7, ba.WeakCall(self.handlemessage, WarnMessage()) + ) else: fuse_time = 3.0 @@ -809,49 +891,55 @@ class Bomb(ba.Actor): tex = factory.sticky_tex else: tex = factory.regular_tex - self.node = ba.newnode('bomb', - delegate=self, - attrs={ - 'position': position, - 'velocity': velocity, - 'model': model, - 'body_scale': self.scale, - 'shadow_size': 0.3, - 'color_texture': tex, - 'sticky': sticky, - 'owner': owner, - 'reflection': rtype, - 'reflection_scale': [rscale], - 'materials': materials - }) + self.node = ba.newnode( + 'bomb', + delegate=self, + attrs={ + 'position': position, + 'velocity': velocity, + 'model': model, + 'body_scale': self.scale, + 'shadow_size': 0.3, + 'color_texture': tex, + 'sticky': sticky, + 'owner': owner, + 'reflection': rtype, + 'reflection_scale': [rscale], + 'materials': materials, + }, + ) - sound = ba.newnode('sound', - owner=self.node, - attrs={ - 'sound': factory.fuse_sound, - 'volume': 0.25 - }) + sound = ba.newnode( + 'sound', + owner=self.node, + attrs={'sound': factory.fuse_sound, 'volume': 0.25}, + ) self.node.connectattr('position', sound, 'position') ba.animate(self.node, 'fuse_length', {0.0: 1.0, fuse_time: 0.0}) # Light the fuse!!! if self.bomb_type not in ('land_mine', 'tnt'): assert fuse_time is not None - ba.timer(fuse_time, - ba.WeakCall(self.handlemessage, ExplodeMessage())) + ba.timer( + fuse_time, ba.WeakCall(self.handlemessage, ExplodeMessage()) + ) - ba.animate(self.node, 'model_scale', { - 0: 0, - 0.2: 1.3 * self.scale, - 0.26: self.scale - }) + ba.animate( + self.node, + 'model_scale', + {0: 0, 0.2: 1.3 * self.scale, 0.26: self.scale}, + ) - def get_source_player(self, - playertype: type[PlayerType]) -> PlayerType | None: + def get_source_player( + self, playertype: type[PlayerType] + ) -> PlayerType | None: """Return the source-player if one exists and is the provided type.""" player: Any = self._source_player - return (player if isinstance(player, playertype) and player.exists() - else None) + return ( + player + if isinstance(player, playertype) and player.exists() + else None + ) def on_expire(self) -> None: super().on_expire() @@ -876,17 +964,22 @@ class Bomb(ba.Actor): # throwing/etc.) node_delegate = node.getdelegate(object) if node: - if (self.bomb_type == 'impact' and - (node is self.owner or - (isinstance(node_delegate, Bomb) and node_delegate.bomb_type - == 'impact' and node_delegate.owner is self.owner))): + if self.bomb_type == 'impact' and ( + node is self.owner + or ( + isinstance(node_delegate, Bomb) + and node_delegate.bomb_type == 'impact' + and node_delegate.owner is self.owner + ) + ): return self.handlemessage(ExplodeMessage()) def _handle_dropped(self) -> None: if self.bomb_type == 'land_mine': self.arm_timer = ba.Timer( - 1.25, ba.WeakCall(self.handlemessage, ArmMessage())) + 1.25, ba.WeakCall(self.handlemessage, ArmMessage()) + ) # Once we've thrown a sticky bomb we can stick to it. elif self.bomb_type == 'sticky': @@ -899,13 +992,17 @@ class Bomb(ba.Actor): def _handle_splat(self) -> None: node = ba.getcollision().opposingnode - if (node is not self.owner - and ba.time() - self._last_sticky_sound_time > 1.0): + if ( + node is not self.owner + and ba.time() - self._last_sticky_sound_time > 1.0 + ): self._last_sticky_sound_time = ba.time() assert self.node - ba.playsound(BombFactory.get().sticky_impact_sound, - 2.0, - position=self.node.position) + ba.playsound( + BombFactory.get().sticky_impact_sound, + 2.0, + position=self.node.position, + ) def add_explode_callback(self, call: Callable[[Bomb, Blast], Any]) -> None: """Add a call to be run when the bomb has exploded. @@ -920,13 +1017,15 @@ class Bomb(ba.Actor): return self._exploded = True if self.node: - blast = Blast(position=self.node.position, - velocity=self.node.velocity, - blast_radius=self.blast_radius, - blast_type=self.bomb_type, - source_player=ba.existing(self._source_player), - hit_type=self.hit_type, - hit_subtype=self.hit_subtype).autoretain() + blast = Blast( + position=self.node.position, + velocity=self.node.velocity, + blast_radius=self.blast_radius, + blast_type=self.bomb_type, + source_player=ba.existing(self._source_player), + hit_type=self.hit_type, + hit_subtype=self.hit_subtype, + ).autoretain() for callback in self._explode_callbacks: callback(self, blast) @@ -937,9 +1036,9 @@ class Bomb(ba.Actor): def _handle_warn(self) -> None: if self.texture_sequence and self.node: self.texture_sequence.rate = 30 - ba.playsound(BombFactory.get().warn_sound, - 0.5, - position=self.node.position) + ba.playsound( + BombFactory.get().warn_sound, 0.5, position=self.node.position + ) def _add_material(self, material: ba.Material) -> None: if not self.node: @@ -947,7 +1046,7 @@ class Bomb(ba.Actor): materials = self.node.materials if material not in materials: assert isinstance(materials, tuple) - self.node.materials = materials + (material, ) + self.node.materials = materials + (material,) def arm(self) -> None: """Arm the bomb (for land-mines and impact-bombs). @@ -960,46 +1059,54 @@ class Bomb(ba.Actor): intex: Sequence[ba.Texture] if self.bomb_type == 'land_mine': intex = (factory.land_mine_lit_tex, factory.land_mine_tex) - self.texture_sequence = ba.newnode('texture_sequence', - owner=self.node, - attrs={ - 'rate': 30, - 'input_textures': intex - }) + self.texture_sequence = ba.newnode( + 'texture_sequence', + owner=self.node, + attrs={'rate': 30, 'input_textures': intex}, + ) ba.timer(0.5, self.texture_sequence.delete) # We now make it explodable. ba.timer( 0.25, - ba.WeakCall(self._add_material, - factory.land_mine_blast_material)) + ba.WeakCall( + self._add_material, factory.land_mine_blast_material + ), + ) elif self.bomb_type == 'impact': - intex = (factory.impact_lit_tex, factory.impact_tex, - factory.impact_tex) - self.texture_sequence = ba.newnode('texture_sequence', - owner=self.node, - attrs={ - 'rate': 100, - 'input_textures': intex - }) + intex = ( + factory.impact_lit_tex, + factory.impact_tex, + factory.impact_tex, + ) + self.texture_sequence = ba.newnode( + 'texture_sequence', + owner=self.node, + attrs={'rate': 100, 'input_textures': intex}, + ) ba.timer( 0.25, - ba.WeakCall(self._add_material, - factory.land_mine_blast_material)) + ba.WeakCall( + self._add_material, factory.land_mine_blast_material + ), + ) else: - raise Exception('arm() should only be called ' - 'on land-mines or impact bombs') - self.texture_sequence.connectattr('output_texture', self.node, - 'color_texture') + raise RuntimeError( + 'arm() should only be called on land-mines or impact bombs' + ) + self.texture_sequence.connectattr( + 'output_texture', self.node, 'color_texture' + ) ba.playsound(factory.activate_sound, 0.5, position=self.node.position) def _handle_hit(self, msg: ba.HitMessage) -> None: - ispunched = (msg.srcnode and msg.srcnode.getnodetype() == 'spaz') + ispunched = msg.srcnode and msg.srcnode.getnodetype() == 'spaz' # Normal bombs are triggered by non-punch impacts; # impact-bombs by all impacts. - if (not self._exploded and - (not ispunched or self.bomb_type in ['impact', 'land_mine'])): + if not self._exploded and ( + not ispunched or self.bomb_type in ['impact', 'land_mine'] + ): # Also lets change the owner of the bomb to whoever is setting # us off. (this way points for big chain reactions go to the @@ -1018,15 +1125,27 @@ class Bomb(ba.Actor): # self.hit_type = msg.hit_type # self.hit_subtype = msg.hit_subtype - ba.timer(0.1 + random.random() * 0.1, - ba.WeakCall(self.handlemessage, ExplodeMessage())) + ba.timer( + 0.1 + random.random() * 0.1, + ba.WeakCall(self.handlemessage, ExplodeMessage()), + ) assert self.node - self.node.handlemessage('impulse', msg.pos[0], msg.pos[1], msg.pos[2], - msg.velocity[0], msg.velocity[1], - msg.velocity[2], msg.magnitude, - msg.velocity_magnitude, msg.radius, 0, - msg.velocity[0], msg.velocity[1], - msg.velocity[2]) + self.node.handlemessage( + 'impulse', + msg.pos[0], + msg.pos[1], + msg.pos[2], + msg.velocity[0], + msg.velocity[1], + msg.velocity[2], + msg.magnitude, + msg.velocity_magnitude, + msg.radius, + 0, + msg.velocity[0], + msg.velocity[1], + msg.velocity[2], + ) if msg.srcnode: pass @@ -1077,9 +1196,9 @@ class TNTSpawner: self._update() # Go with slightly more than 1 second to avoid timer stacking. - self._update_timer = ba.Timer(1.1, - ba.WeakCall(self._update), - repeat=True) + self._update_timer = ba.Timer( + 1.1, ba.WeakCall(self._update), repeat=True + ) def _update(self) -> None: tnt_alive = self._tnt is not None and self._tnt.node diff --git a/dist/ba_data/python/bastd/actor/controlsguide.py b/dist/ba_data/python/bastd/actor/controlsguide.py index 40e82f6..e7779f9 100644 --- a/dist/ba_data/python/bastd/actor/controlsguide.py +++ b/dist/ba_data/python/bastd/actor/controlsguide.py @@ -23,12 +23,14 @@ class ControlsGuide(ba.Actor): be newbies watching. """ - def __init__(self, - position: tuple[float, float] = (390.0, 120.0), - scale: float = 1.0, - delay: float = 0.0, - lifespan: float | None = None, - bright: bool = False): + def __init__( + self, + position: tuple[float, float] = (390.0, 120.0), + scale: float = 1.0, + delay: float = 0.0, + lifespan: float | None = None, + bright: bool = False, + ): """Instantiate an overlay. delay: is the time in seconds before the overlay fades in. @@ -62,19 +64,31 @@ class ControlsGuide(ba.Actor): if ba.app.iircade_mode: xtweak = 0.2 ytweak = 0.2 - jump_pos = (position[0] + offs * (-1.2 + xtweak), - position[1] + offs * (0.1 + ytweak)) - bomb_pos = (position[0] + offs * (0.0 + xtweak), - position[1] + offs * (0.5 + ytweak)) - punch_pos = (position[0] + offs * (1.2 + xtweak), - position[1] + offs * (0.5 + ytweak)) + jump_pos = ( + position[0] + offs * (-1.2 + xtweak), + position[1] + offs * (0.1 + ytweak), + ) + bomb_pos = ( + position[0] + offs * (0.0 + xtweak), + position[1] + offs * (0.5 + ytweak), + ) + punch_pos = ( + position[0] + offs * (1.2 + xtweak), + position[1] + offs * (0.5 + ytweak), + ) - pickup_pos = (position[0] + offs * (-1.4 + xtweak), - position[1] + offs * (-1.2 + ytweak)) - extra_pos_1 = (position[0] + offs * (-0.2 + xtweak), - position[1] + offs * (-0.8 + ytweak)) - extra_pos_2 = (position[0] + offs * (1.0 + xtweak), - position[1] + offs * (-0.8 + ytweak)) + pickup_pos = ( + position[0] + offs * (-1.4 + xtweak), + position[1] + offs * (-1.2 + ytweak), + ) + extra_pos_1 = ( + position[0] + offs * (-0.2 + xtweak), + position[1] + offs * (-0.8 + ytweak), + ) + extra_pos_2 = ( + position[0] + offs * (1.0 + xtweak), + position[1] + offs * (-0.8 + ytweak), + ) self._force_hide_button_names = True else: punch_pos = (position[0] - offs * 1.1, position[1]) @@ -86,25 +100,32 @@ class ControlsGuide(ba.Actor): self._force_hide_button_names = False if show_title: - self._title_text_pos_top = (position[0], - position[1] + 139.0 * scale) - self._title_text_pos_bottom = (position[0], - position[1] + 139.0 * scale) + self._title_text_pos_top = ( + position[0], + position[1] + 139.0 * scale, + ) + self._title_text_pos_bottom = ( + position[0], + position[1] + 139.0 * scale, + ) clr = (1, 1, 1) if bright else (0.7, 0.7, 0.7) - tval = ba.Lstr(value='${A}:', - subs=[('${A}', ba.Lstr(resource='controlsText'))]) - self._title_text = ba.newnode('text', - attrs={ - 'text': tval, - 'host_only': True, - 'scale': 1.1 * scale, - 'shadow': 0.5, - 'flatness': 1.0, - 'maxwidth': 480, - 'v_align': 'center', - 'h_align': 'center', - 'color': clr - }) + tval = ba.Lstr( + value='${A}:', subs=[('${A}', ba.Lstr(resource='controlsText'))] + ) + self._title_text = ba.newnode( + 'text', + attrs={ + 'text': tval, + 'host_only': True, + 'scale': 1.1 * scale, + 'shadow': 0.5, + 'flatness': 1.0, + 'maxwidth': 480, + 'v_align': 'center', + 'h_align': 'center', + 'color': clr, + }, + ) else: self._title_text = None pos = jump_pos @@ -118,20 +139,23 @@ class ControlsGuide(ba.Actor): 'vr_depth': 10, 'position': pos, 'scale': (image_size, image_size), - 'color': clr - }) - self._jump_text = ba.newnode('text', - attrs={ - 'v_align': 'top', - 'h_align': 'center', - 'scale': 1.5 * scale, - 'flatness': 1.0, - 'host_only': True, - 'shadow': 1.0, - 'maxwidth': maxw, - 'position': (pos[0], pos[1] - offs5), - 'color': clr - }) + 'color': clr, + }, + ) + self._jump_text = ba.newnode( + 'text', + attrs={ + 'v_align': 'top', + 'h_align': 'center', + 'scale': 1.5 * scale, + 'flatness': 1.0, + 'host_only': True, + 'shadow': 1.0, + 'maxwidth': maxw, + 'position': (pos[0], pos[1] - offs5), + 'color': clr, + }, + ) clr = (0.2, 0.6, 1) if ouya else (1, 0.7, 0.3) pos = punch_pos self._punch_image = ba.newnode( @@ -143,20 +167,23 @@ class ControlsGuide(ba.Actor): 'vr_depth': 10, 'position': pos, 'scale': (image_size, image_size), - 'color': clr - }) - self._punch_text = ba.newnode('text', - attrs={ - 'v_align': 'top', - 'h_align': 'center', - 'scale': 1.5 * scale, - 'flatness': 1.0, - 'host_only': True, - 'shadow': 1.0, - 'maxwidth': maxw, - 'position': (pos[0], pos[1] - offs5), - 'color': clr - }) + 'color': clr, + }, + ) + self._punch_text = ba.newnode( + 'text', + attrs={ + 'v_align': 'top', + 'h_align': 'center', + 'scale': 1.5 * scale, + 'flatness': 1.0, + 'host_only': True, + 'shadow': 1.0, + 'maxwidth': maxw, + 'position': (pos[0], pos[1] - offs5), + 'color': clr, + }, + ) pos = bomb_pos clr = (1, 0.3, 0.3) self._bomb_image = ba.newnode( @@ -168,20 +195,23 @@ class ControlsGuide(ba.Actor): 'vr_depth': 10, 'position': pos, 'scale': (image_size, image_size), - 'color': clr - }) - self._bomb_text = ba.newnode('text', - attrs={ - 'h_align': 'center', - 'v_align': 'top', - 'scale': 1.5 * scale, - 'flatness': 1.0, - 'host_only': True, - 'shadow': 1.0, - 'maxwidth': maxw, - 'position': (pos[0], pos[1] - offs5), - 'color': clr - }) + 'color': clr, + }, + ) + self._bomb_text = ba.newnode( + 'text', + attrs={ + 'h_align': 'center', + 'v_align': 'top', + 'scale': 1.5 * scale, + 'flatness': 1.0, + 'host_only': True, + 'shadow': 1.0, + 'maxwidth': maxw, + 'position': (pos[0], pos[1] - offs5), + 'color': clr, + }, + ) pos = pickup_pos clr = (1, 0.8, 0.3) if ouya else (0.8, 0.5, 1) self._pickup_image = ba.newnode( @@ -193,25 +223,27 @@ class ControlsGuide(ba.Actor): 'vr_depth': 10, 'position': pos, 'scale': (image_size, image_size), - 'color': clr - }) - self._pick_up_text = ba.newnode('text', - attrs={ - 'v_align': 'top', - 'h_align': 'center', - 'scale': 1.5 * scale, - 'flatness': 1.0, - 'host_only': True, - 'shadow': 1.0, - 'maxwidth': maxw, - 'position': - (pos[0], pos[1] - offs5), - 'color': clr - }) + 'color': clr, + }, + ) + self._pick_up_text = ba.newnode( + 'text', + attrs={ + 'v_align': 'top', + 'h_align': 'center', + 'scale': 1.5 * scale, + 'flatness': 1.0, + 'host_only': True, + 'shadow': 1.0, + 'maxwidth': maxw, + 'position': (pos[0], pos[1] - offs5), + 'color': clr, + }, + ) clr = (0.9, 0.9, 2.0, 1.0) if bright else (0.8, 0.8, 2.0, 1.0) self._run_text_pos_top = (position[0], position[1] - 135.0 * scale) self._run_text_pos_bottom = (position[0], position[1] - 172.0 * scale) - sval = (1.0 * scale if ba.app.vr_mode else 0.8 * scale) + sval = 1.0 * scale if ba.app.vr_mode else 0.8 * scale self._run_text = ba.newnode( 'text', attrs={ @@ -222,20 +254,23 @@ class ControlsGuide(ba.Actor): 'maxwidth': 380, 'v_align': 'top', 'h_align': 'center', - 'color': clr - }) + 'color': clr, + }, + ) clr = (1, 1, 1) if bright else (0.7, 0.7, 0.7) - self._extra_text = ba.newnode('text', - attrs={ - 'scale': 0.8 * scale, - 'host_only': True, - 'shadow': 0.5, - 'flatness': 1.0, - 'maxwidth': 380, - 'v_align': 'top', - 'h_align': 'center', - 'color': clr - }) + self._extra_text = ba.newnode( + 'text', + attrs={ + 'scale': 0.8 * scale, + 'host_only': True, + 'shadow': 0.5, + 'flatness': 1.0, + 'maxwidth': 380, + 'v_align': 'top', + 'h_align': 'center', + 'color': clr, + }, + ) if extra_pos_1 is not None: self._extra_image_1: ba.Node | None = ba.newnode( @@ -247,8 +282,9 @@ class ControlsGuide(ba.Actor): 'vr_depth': 10, 'position': extra_pos_1, 'scale': (image_size, image_size), - 'color': (0.5, 0.5, 0.5) - }) + 'color': (0.5, 0.5, 0.5), + }, + ) else: self._extra_image_1 = None if extra_pos_2 is not None: @@ -261,16 +297,23 @@ class ControlsGuide(ba.Actor): 'vr_depth': 10, 'position': extra_pos_2, 'scale': (image_size, image_size), - 'color': (0.5, 0.5, 0.5) - }) + 'color': (0.5, 0.5, 0.5), + }, + ) else: self._extra_image_2 = None self._nodes = [ - self._bomb_image, self._bomb_text, self._punch_image, - self._punch_text, self._jump_image, self._jump_text, - self._pickup_image, self._pick_up_text, self._run_text, - self._extra_text + self._bomb_image, + self._bomb_text, + self._punch_image, + self._punch_text, + self._jump_image, + self._jump_text, + self._pickup_image, + self._pick_up_text, + self._run_text, + self._extra_text, ] if show_title: assert self._title_text @@ -304,10 +347,11 @@ class ControlsGuide(ba.Actor): if self._lifespan is not None: self._cancel_timer = ba.Timer( self._lifespan, - ba.WeakCall(self.handlemessage, ba.DieMessage(immediate=True))) - self._fade_in_timer = ba.Timer(1.0, - ba.WeakCall(self._check_fade_in), - repeat=True) + ba.WeakCall(self.handlemessage, ba.DieMessage(immediate=True)), + ) + self._fade_in_timer = ba.Timer( + 1.0, ba.WeakCall(self._check_fade_in), repeat=True + ) self._check_fade_in() # Do one check immediately. def _check_fade_in(self) -> None: @@ -318,7 +362,8 @@ class ControlsGuide(ba.Actor): # (otherwise it is confusing to see the touchscreen buttons right # next to our display buttons) touchscreen: ba.InputDevice | None = ba.internal.getinputdevice( - 'TouchScreen', '#1', doraise=False) + 'TouchScreen', '#1', doraise=False + ) if touchscreen is not None: # We look at the session's players; not the activity's. @@ -335,10 +380,18 @@ class ControlsGuide(ba.Actor): # Only count this one if it has non-empty button names # (filters out wiimotes, the remote-app, etc). for device in input_devices: - for name in ('buttonPunch', 'buttonJump', 'buttonBomb', - 'buttonPickUp'): - if self._meaningful_button_name( - device, get_device_value(device, name)) != '': + for name in ( + 'buttonPunch', + 'buttonJump', + 'buttonBomb', + 'buttonPickUp', + ): + if ( + self._meaningful_button_name( + device, get_device_value(device, name) + ) + != '' + ): fade_in = True break if fade_in: @@ -357,18 +410,20 @@ class ControlsGuide(ba.Actor): # If we were given a lifespan, transition out after it. if self._lifespan is not None: - ba.timer(self._lifespan, - ba.WeakCall(self.handlemessage, ba.DieMessage())) + ba.timer( + self._lifespan, ba.WeakCall(self.handlemessage, ba.DieMessage()) + ) self._update() - self._update_timer = ba.Timer(1.0, - ba.WeakCall(self._update), - repeat=True) + self._update_timer = ba.Timer( + 1.0, ba.WeakCall(self._update), repeat=True + ) def _update(self) -> None: # pylint: disable=too-many-statements # pylint: disable=too-many-branches # pylint: disable=too-many-locals from ba.internal import get_device_value, get_remote_app_name + if self._dead: return punch_button_names = set() @@ -389,11 +444,12 @@ class ControlsGuide(ba.Actor): input_devices.append(kbd) # We word things specially if we have nothing but keyboards. - all_keyboards = (input_devices - and all(i.name == 'Keyboard' for i in input_devices)) - only_remote = (len(input_devices) == 1 - and all(i.name == 'Amazon Fire TV Remote' - for i in input_devices)) + all_keyboards = input_devices and all( + i.name == 'Keyboard' for i in input_devices + ) + only_remote = len(input_devices) == 1 and all( + i.name == 'Amazon Fire TV Remote' for i in input_devices + ) right_button_names = set() left_button_names = set() @@ -408,40 +464,57 @@ class ControlsGuide(ba.Actor): if all_keyboards: right_button_names.add( device.get_button_name( - get_device_value(device, 'buttonRight'))) + get_device_value(device, 'buttonRight') + ) + ) left_button_names.add( device.get_button_name( - get_device_value(device, 'buttonLeft'))) + get_device_value(device, 'buttonLeft') + ) + ) down_button_names.add( device.get_button_name( - get_device_value(device, 'buttonDown'))) + get_device_value(device, 'buttonDown') + ) + ) up_button_names.add( - device.get_button_name(get_device_value( - device, 'buttonUp'))) + device.get_button_name(get_device_value(device, 'buttonUp')) + ) # Ignore empty values; things like the remote app or # wiimotes can return these. bname = self._meaningful_button_name( - device, get_device_value(device, 'buttonPunch')) + device, get_device_value(device, 'buttonPunch') + ) if bname != '': punch_button_names.add(bname) bname = self._meaningful_button_name( - device, get_device_value(device, 'buttonJump')) + device, get_device_value(device, 'buttonJump') + ) if bname != '': jump_button_names.add(bname) bname = self._meaningful_button_name( - device, get_device_value(device, 'buttonBomb')) + device, get_device_value(device, 'buttonBomb') + ) if bname != '': bomb_button_names.add(bname) bname = self._meaningful_button_name( - device, get_device_value(device, 'buttonPickUp')) + device, get_device_value(device, 'buttonPickUp') + ) if bname != '': pickup_button_names.add(bname) # If we have no values yet, we may want to throw out some sane # defaults. - if all(not lst for lst in (punch_button_names, jump_button_names, - bomb_button_names, pickup_button_names)): + if all( + not lst + for lst in ( + punch_button_names, + jump_button_names, + bomb_button_names, + pickup_button_names, + ) + ): # Otherwise on android show standard buttons. if ba.app.platform == 'android': punch_button_names.add('X') @@ -451,24 +524,42 @@ class ControlsGuide(ba.Actor): run_text = ba.Lstr( value='${R}: ${B}', - subs=[('${R}', ba.Lstr(resource='runText')), - ('${B}', - ba.Lstr(resource='holdAnyKeyText' - if all_keyboards else 'holdAnyButtonText'))]) + subs=[ + ('${R}', ba.Lstr(resource='runText')), + ( + '${B}', + ba.Lstr( + resource='holdAnyKeyText' + if all_keyboards + else 'holdAnyButtonText' + ), + ), + ], + ) # If we're all keyboards, lets show move keys too. - if (all_keyboards and len(up_button_names) == 1 - and len(down_button_names) == 1 and len(left_button_names) == 1 - and len(right_button_names) == 1): + if ( + all_keyboards + and len(up_button_names) == 1 + and len(down_button_names) == 1 + and len(left_button_names) == 1 + and len(right_button_names) == 1 + ): up_text = list(up_button_names)[0] down_text = list(down_button_names)[0] left_text = list(left_button_names)[0] right_text = list(right_button_names)[0] - run_text = ba.Lstr(value='${M}: ${U}, ${L}, ${D}, ${R}\n${RUN}', - subs=[('${M}', ba.Lstr(resource='moveText')), - ('${U}', up_text), ('${L}', left_text), - ('${D}', down_text), ('${R}', right_text), - ('${RUN}', run_text)]) + run_text = ba.Lstr( + value='${M}: ${U}, ${L}, ${D}, ${R}\n${RUN}', + subs=[ + ('${M}', ba.Lstr(resource='moveText')), + ('${U}', up_text), + ('${L}', left_text), + ('${D}', down_text), + ('${R}', right_text), + ('${RUN}', run_text), + ], + ) if self._force_hide_button_names: jump_button_names.clear() @@ -479,9 +570,10 @@ class ControlsGuide(ba.Actor): self._run_text.text = run_text w_text: ba.Lstr | str if only_remote and self._lifespan is None: - w_text = ba.Lstr(resource='fireTVRemoteWarningText', - subs=[('${REMOTE_APP_NAME}', - get_remote_app_name())]) + w_text = ba.Lstr( + resource='fireTVRemoteWarningText', + subs=[('${REMOTE_APP_NAME}', get_remote_app_name())], + ) else: w_text = '' self._extra_text.text = w_text @@ -497,12 +589,16 @@ class ControlsGuide(ba.Actor): self._jump_text.text = tval if tval == '': self._run_text.position = self._run_text_pos_top - self._extra_text.position = (self._run_text_pos_top[0], - self._run_text_pos_top[1] - 50) + self._extra_text.position = ( + self._run_text_pos_top[0], + self._run_text_pos_top[1] - 50, + ) else: self._run_text.position = self._run_text_pos_bottom - self._extra_text.position = (self._run_text_pos_bottom[0], - self._run_text_pos_bottom[1] - 50) + self._extra_text.position = ( + self._run_text_pos_bottom[0], + self._run_text_pos_bottom[1] - 50, + ) if len(bomb_button_names) == 1: self._bomb_text.text = list(bomb_button_names)[0] else: diff --git a/dist/ba_data/python/bastd/actor/flag.py b/dist/ba_data/python/bastd/actor/flag.py index deaa620..b1c5693 100644 --- a/dist/ba_data/python/bastd/actor/flag.py +++ b/dist/ba_data/python/bastd/actor/flag.py @@ -99,8 +99,10 @@ class FlagFactory: 'or', ('they_dont_have_material', shared.footing_material), ), - actions=(('modify_part_collision', 'collide', False), - ('modify_part_collision', 'physical', False)), + actions=( + ('modify_part_collision', 'collide', False), + ('modify_part_collision', 'physical', False), + ), ) self.flag_texture = ba.gettexture('flagColor') @@ -164,12 +166,14 @@ class Flag(ba.Actor): Can be stationary or carry-able by players. """ - def __init__(self, - position: Sequence[float] = (0.0, 1.0, 0.0), - color: Sequence[float] = (1.0, 1.0, 1.0), - materials: Sequence[ba.Material] | None = None, - touchable: bool = True, - dropped_timeout: int | None = None): + def __init__( + self, + position: Sequence[float] = (0.0, 1.0, 0.0), + color: Sequence[float] = (1.0, 1.0, 1.0), + materials: Sequence[ba.Material] | None = None, + touchable: bool = True, + dropped_timeout: int | None = None, + ): """Instantiate a flag. If 'touchable' is False, the flag will only touch terrain; @@ -198,18 +202,20 @@ class Flag(ba.Actor): if not touchable: materials = [factory.no_hit_material] + materials - finalmaterials = ([shared.object_material, factory.flagmaterial] + - materials) - self.node = ba.newnode('flag', - attrs={ - 'position': - (position[0], position[1] + 0.75, - position[2]), - 'color_texture': factory.flag_texture, - 'color': color, - 'materials': finalmaterials - }, - delegate=self) + finalmaterials = [ + shared.object_material, + factory.flagmaterial, + ] + materials + self.node = ba.newnode( + 'flag', + attrs={ + 'position': (position[0], position[1] + 0.75, position[2]), + 'color_texture': factory.flag_texture, + 'color': color, + 'materials': finalmaterials, + }, + delegate=self, + ) if dropped_timeout is not None: dropped_timeout = int(dropped_timeout) @@ -217,19 +223,21 @@ class Flag(ba.Actor): self._counter: ba.Node | None if self._dropped_timeout is not None: self._count = self._dropped_timeout - self._tick_timer = ba.Timer(1.0, - call=ba.WeakCall(self._tick), - repeat=True) - self._counter = ba.newnode('text', - owner=self.node, - attrs={ - 'in_world': True, - 'color': (1, 1, 1, 0.7), - 'scale': 0.015, - 'shadow': 0.5, - 'flatness': 1.0, - 'h_align': 'center' - }) + self._tick_timer = ba.Timer( + 1.0, call=ba.WeakCall(self._tick), repeat=True + ) + self._counter = ba.newnode( + 'text', + owner=self.node, + attrs={ + 'in_world': True, + 'color': (1, 1, 1, 0.7), + 'scale': 0.015, + 'shadow': 0.5, + 'flatness': 1.0, + 'h_align': 'center', + }, + ) else: self._counter = None @@ -248,9 +256,13 @@ class Flag(ba.Actor): # until then. if not self._has_moved: nodepos = self.node.position - if (max( + if ( + max( abs(nodepos[i] - self._initial_position[i]) - for i in list(range(3))) > 1.0): + for i in list(range(3)) + ) + > 1.0 + ): self._has_moved = True if self._held_count > 0 or not self._has_moved: @@ -263,8 +275,11 @@ class Flag(ba.Actor): if self._count <= 10: nodepos = self.node.position assert self._counter - self._counter.position = (nodepos[0], nodepos[1] + 1.3, - nodepos[2]) + self._counter.position = ( + nodepos[0], + nodepos[1] + 1.3, + nodepos[2], + ) self._counter.text = str(self._count) if self._count < 1: self.handlemessage(ba.DieMessage()) @@ -275,10 +290,9 @@ class Flag(ba.Actor): def _hide_score_text(self) -> None: assert self._score_text is not None assert isinstance(self._score_text.scale, float) - ba.animate(self._score_text, 'scale', { - 0: self._score_text.scale, - 0.2: 0 - }) + ba.animate( + self._score_text, 'scale', {0: self._score_text.scale, 0.2: 0} + ) def set_score_text(self, text: str) -> None: """Show a message over the flag; handy for scores.""" @@ -286,23 +300,24 @@ class Flag(ba.Actor): return if not self._score_text: start_scale = 0.0 - math = ba.newnode('math', - owner=self.node, - attrs={ - 'input1': (0, 1.4, 0), - 'operation': 'add' - }) + math = ba.newnode( + 'math', + owner=self.node, + attrs={'input1': (0, 1.4, 0), 'operation': 'add'}, + ) self.node.connectattr('position', math, 'input2') - self._score_text = ba.newnode('text', - owner=self.node, - attrs={ - 'text': text, - 'in_world': True, - 'scale': 0.02, - 'shadow': 0.5, - 'flatness': 1.0, - 'h_align': 'center' - }) + self._score_text = ba.newnode( + 'text', + owner=self.node, + attrs={ + 'text': text, + 'in_world': True, + 'scale': 0.02, + 'shadow': 0.5, + 'flatness': 1.0, + 'h_align': 'center', + }, + ) math.connectattr('output', self._score_text, 'position') else: assert isinstance(self._score_text.scale, float) @@ -311,7 +326,8 @@ class Flag(ba.Actor): self._score_text.color = ba.safecolor(self.node.color) ba.animate(self._score_text, 'scale', {0: start_scale, 0.2: 0.02}) self._score_text_hide_timer = ba.Timer( - 1.0, ba.WeakCall(self._hide_score_text)) + 1.0, ba.WeakCall(self._hide_score_text) + ) def handlemessage(self, msg: Any) -> Any: assert not self.expired @@ -324,10 +340,21 @@ class Flag(ba.Actor): assert self.node assert msg.force_direction is not None self.node.handlemessage( - 'impulse', msg.pos[0], msg.pos[1], msg.pos[2], msg.velocity[0], - msg.velocity[1], msg.velocity[2], msg.magnitude, - msg.velocity_magnitude, msg.radius, 0, msg.force_direction[0], - msg.force_direction[1], msg.force_direction[2]) + 'impulse', + msg.pos[0], + msg.pos[1], + msg.pos[2], + msg.velocity[0], + msg.velocity[1], + msg.velocity[2], + msg.magnitude, + msg.velocity_magnitude, + msg.radius, + 0, + msg.force_direction[0], + msg.force_direction[1], + msg.force_direction[2], + ) elif isinstance(msg, ba.PickedUpMessage): self._held_count += 1 if self._held_count == 1 and self._counter is not None: diff --git a/dist/ba_data/python/bastd/actor/image.py b/dist/ba_data/python/bastd/actor/image.py index 71f23ef..51774b9 100644 --- a/dist/ba_data/python/bastd/actor/image.py +++ b/dist/ba_data/python/bastd/actor/image.py @@ -18,6 +18,7 @@ class Image(ba.Actor): class Transition(Enum): """Transition types we support.""" + FADE_IN = 'fade_in' IN_RIGHT = 'in_right' IN_LEFT = 'in_left' @@ -27,25 +28,28 @@ class Image(ba.Actor): class Attach(Enum): """Attach types we support.""" + CENTER = 'center' TOP_CENTER = 'topCenter' TOP_LEFT = 'topLeft' BOTTOM_CENTER = 'bottomCenter' - def __init__(self, - texture: ba.Texture | dict[str, Any], - position: tuple[float, float] = (0, 0), - transition: Transition | None = None, - transition_delay: float = 0.0, - attach: Attach = Attach.CENTER, - color: Sequence[float] = (1.0, 1.0, 1.0, 1.0), - scale: tuple[float, float] = (100.0, 100.0), - transition_out_delay: float | None = None, - model_opaque: ba.Model | None = None, - model_transparent: ba.Model | None = None, - vr_depth: float = 0.0, - host_only: bool = False, - front: bool = False): + def __init__( + self, + texture: ba.Texture | dict[str, Any], + position: tuple[float, float] = (0, 0), + transition: Transition | None = None, + transition_delay: float = 0.0, + attach: Attach = Attach.CENTER, + color: Sequence[float] = (1.0, 1.0, 1.0, 1.0), + scale: tuple[float, float] = (100.0, 100.0), + transition_out_delay: float | None = None, + model_opaque: ba.Model | None = None, + model_transparent: ba.Model | None = None, + vr_depth: float = 0.0, + host_only: bool = False, + front: bool = False, + ): # pylint: disable=too-many-statements # pylint: disable=too-many-branches # pylint: disable=too-many-locals @@ -66,22 +70,24 @@ class Image(ba.Actor): tint_texture = None mask_texture = None - self.node = ba.newnode('image', - attrs={ - 'texture': texture, - 'tint_color': tint_color, - 'tint_texture': tint_texture, - 'position': position, - 'vr_depth': vr_depth, - 'scale': scale, - 'mask_texture': mask_texture, - 'color': color, - 'absolute_scale': True, - 'host_only': host_only, - 'front': front, - 'attach': attach.value - }, - delegate=self) + self.node = ba.newnode( + 'image', + attrs={ + 'texture': texture, + 'tint_color': tint_color, + 'tint_texture': tint_texture, + 'position': position, + 'vr_depth': vr_depth, + 'scale': scale, + 'mask_texture': mask_texture, + 'color': color, + 'absolute_scale': True, + 'host_only': host_only, + 'front': front, + 'attach': attach.value, + }, + delegate=self, + ) if model_opaque is not None: self.node.model_opaque = model_opaque @@ -95,13 +101,13 @@ class Image(ba.Actor): keys[transition_delay + transition_out_delay] = color[3] keys[transition_delay + transition_out_delay + 0.5] = 0 ba.animate(self.node, 'opacity', keys) - cmb = self.position_combine = ba.newnode('combine', - owner=self.node, - attrs={'size': 2}) + cmb = self.position_combine = ba.newnode( + 'combine', owner=self.node, attrs={'size': 2} + ) if transition is self.Transition.IN_RIGHT: keys = { transition_delay: position[0] + 1200, - transition_delay + 0.2: position[0] + transition_delay + 0.2: position[0], } o_keys = {transition_delay: 0.0, transition_delay + 0.05: 1.0} ba.animate(cmb, 'input0', keys) @@ -110,32 +116,27 @@ class Image(ba.Actor): elif transition is self.Transition.IN_LEFT: keys = { transition_delay: position[0] - 1200, - transition_delay + 0.2: position[0] + transition_delay + 0.2: position[0], } o_keys = {transition_delay: 0.0, transition_delay + 0.05: 1.0} if transition_out_delay is not None: keys[transition_delay + transition_out_delay] = position[0] - keys[transition_delay + transition_out_delay + - 200] = -position[0] - 1200 + keys[transition_delay + transition_out_delay + 200] = ( + -position[0] - 1200 + ) o_keys[transition_delay + transition_out_delay + 0.15] = 1.0 o_keys[transition_delay + transition_out_delay + 0.2] = 0.0 ba.animate(cmb, 'input0', keys) cmb.input1 = position[1] ba.animate(self.node, 'opacity', o_keys) elif transition is self.Transition.IN_BOTTOM_SLOW: - keys = { - transition_delay: -400, - transition_delay + 3.5: position[1] - } + keys = {transition_delay: -400, transition_delay + 3.5: position[1]} o_keys = {transition_delay: 0.0, transition_delay + 2.0: 1.0} cmb.input0 = position[0] ba.animate(cmb, 'input1', keys) ba.animate(self.node, 'opacity', o_keys) elif transition is self.Transition.IN_BOTTOM: - keys = { - transition_delay: -400, - transition_delay + 0.2: position[1] - } + keys = {transition_delay: -400, transition_delay + 0.2: position[1]} o_keys = {transition_delay: 0.0, transition_delay + 0.05: 1.0} if transition_out_delay is not None: keys[transition_delay + transition_out_delay] = position[1] @@ -159,8 +160,10 @@ class Image(ba.Actor): # If we're transitioning out, die at the end of it. if transition_out_delay is not None: - ba.timer(transition_delay + transition_out_delay + 1.0, - ba.WeakCall(self.handlemessage, ba.DieMessage())) + ba.timer( + transition_delay + transition_out_delay + 1.0, + ba.WeakCall(self.handlemessage, ba.DieMessage()), + ) def handlemessage(self, msg: Any) -> Any: assert not self.expired diff --git a/dist/ba_data/python/bastd/actor/onscreencountdown.py b/dist/ba_data/python/bastd/actor/onscreencountdown.py index 618c0c4..2210f51 100644 --- a/dist/ba_data/python/bastd/actor/onscreencountdown.py +++ b/dist/ba_data/python/bastd/actor/onscreencountdown.py @@ -20,32 +20,34 @@ class OnScreenCountdown(ba.Actor): Useful for time-based games that count down to zero. """ - def __init__(self, - duration: int, - endcall: Callable[[], Any] | None = None): + def __init__(self, duration: int, endcall: Callable[[], Any] | None = None): """Duration is provided in seconds.""" super().__init__() self._timeremaining = duration self._ended = False self._endcall = endcall - self.node = ba.newnode('text', - attrs={ - 'v_attach': 'top', - 'h_attach': 'center', - 'h_align': 'center', - 'color': (1, 1, 0.5, 1), - 'flatness': 0.5, - 'shadow': 0.5, - 'position': (0, -70), - 'scale': 1.4, - 'text': '' - }) - self.inputnode = ba.newnode('timedisplay', - attrs={ - 'time2': duration * 1000, - 'timemax': duration * 1000, - 'timemin': 0 - }) + self.node = ba.newnode( + 'text', + attrs={ + 'v_attach': 'top', + 'h_attach': 'center', + 'h_align': 'center', + 'color': (1, 1, 0.5, 1), + 'flatness': 0.5, + 'shadow': 0.5, + 'position': (0, -70), + 'scale': 1.4, + 'text': '', + }, + ) + self.inputnode = ba.newnode( + 'timedisplay', + attrs={ + 'time2': duration * 1000, + 'timemax': duration * 1000, + 'timemin': 0, + }, + ) self.inputnode.connectattr('output', self.node, 'text') self._countdownsounds = { 10: ba.getsound('announceTen'), @@ -57,7 +59,7 @@ class OnScreenCountdown(ba.Actor): 4: ba.getsound('announceFour'), 3: ba.getsound('announceThree'), 2: ba.getsound('announceTwo'), - 1: ba.getsound('announceOne') + 1: ba.getsound('announceOne'), } self._timer: ba.Timer | None = None @@ -65,8 +67,9 @@ class OnScreenCountdown(ba.Actor): """Start the timer.""" globalsnode = ba.getactivity().globalsnode globalsnode.connectattr('time', self.inputnode, 'time1') - self.inputnode.time2 = (globalsnode.time + - (self._timeremaining + 1) * 1000) + self.inputnode.time2 = ( + globalsnode.time + (self._timeremaining + 1) * 1000 + ) self._timer = ba.Timer(1.0, self._update, repeat=True) def on_expire(self) -> None: diff --git a/dist/ba_data/python/bastd/actor/onscreentimer.py b/dist/ba_data/python/bastd/actor/onscreentimer.py index bf5e8f1..99deae5 100644 --- a/dist/ba_data/python/bastd/actor/onscreentimer.py +++ b/dist/ba_data/python/bastd/actor/onscreentimer.py @@ -22,23 +22,23 @@ class OnScreenTimer(ba.Actor): def __init__(self) -> None: super().__init__() self._starttime_ms: int | None = None - self.node = ba.newnode('text', - attrs={ - 'v_attach': 'top', - 'h_attach': 'center', - 'h_align': 'center', - 'color': (1, 1, 0.5, 1), - 'flatness': 0.5, - 'shadow': 0.5, - 'position': (0, -70), - 'scale': 1.4, - 'text': '' - }) - self.inputnode = ba.newnode('timedisplay', - attrs={ - 'timemin': 0, - 'showsubseconds': True - }) + self.node = ba.newnode( + 'text', + attrs={ + 'v_attach': 'top', + 'h_attach': 'center', + 'h_align': 'center', + 'color': (1, 1, 0.5, 1), + 'flatness': 0.5, + 'shadow': 0.5, + 'position': (0, -70), + 'scale': 1.4, + 'text': '', + }, + ) + self.inputnode = ba.newnode( + 'timedisplay', attrs={'timemin': 0, 'showsubseconds': True} + ) self.inputnode.connectattr('output', self.node, 'text') def start(self) -> None: @@ -47,16 +47,19 @@ class OnScreenTimer(ba.Actor): assert isinstance(tval, int) self._starttime_ms = tval self.inputnode.time1 = self._starttime_ms - ba.getactivity().globalsnode.connectattr('time', self.inputnode, - 'time2') + ba.getactivity().globalsnode.connectattr( + 'time', self.inputnode, 'time2' + ) def has_started(self) -> bool: """Return whether this timer has started yet.""" return self._starttime_ms is not None - def stop(self, - endtime: int | float | None = None, - timeformat: ba.TimeFormat = ba.TimeFormat.SECONDS) -> None: + def stop( + self, + endtime: int | float | None = None, + timeformat: ba.TimeFormat = ba.TimeFormat.SECONDS, + ) -> None: """End the timer. If 'endtime' is not None, it is used when calculating @@ -85,19 +88,19 @@ class OnScreenTimer(ba.Actor): # Overloads so type checker knows our exact return type based in args. @overload def getstarttime( - self, - timeformat: Literal[ba.TimeFormat.SECONDS] = ba.TimeFormat.SECONDS + self, timeformat: Literal[ba.TimeFormat.SECONDS] = ba.TimeFormat.SECONDS ) -> float: ... @overload - def getstarttime(self, - timeformat: Literal[ba.TimeFormat.MILLISECONDS]) -> int: + def getstarttime( + self, timeformat: Literal[ba.TimeFormat.MILLISECONDS] + ) -> int: ... def getstarttime( - self, - timeformat: ba.TimeFormat = ba.TimeFormat.SECONDS) -> int | float: + self, timeformat: ba.TimeFormat = ba.TimeFormat.SECONDS + ) -> int | float: """Return the sim-time when start() was called. Time will be returned in seconds if timeformat is SECONDS or diff --git a/dist/ba_data/python/bastd/actor/playerspaz.py b/dist/ba_data/python/bastd/actor/playerspaz.py index 4687b57..92ed7dd 100644 --- a/dist/ba_data/python/bastd/actor/playerspaz.py +++ b/dist/ba_data/python/bastd/actor/playerspaz.py @@ -45,12 +45,14 @@ class PlayerSpaz(Spaz): to the current ba.Activity. """ - def __init__(self, - player: ba.Player, - color: Sequence[float] = (1.0, 1.0, 1.0), - highlight: Sequence[float] = (0.5, 0.5, 0.5), - character: str = 'Spaz', - powerups_expire: bool = True): + def __init__( + self, + player: ba.Player, + color: Sequence[float] = (1.0, 1.0, 1.0), + highlight: Sequence[float] = (0.5, 0.5, 0.5), + character: str = 'Spaz', + powerups_expire: bool = True, + ): """Create a spaz for the provided ba.Player. Note: this does not wire up any controls; @@ -58,12 +60,14 @@ class PlayerSpaz(Spaz): """ character=modifyspaz.getCharacter(player,character) - super().__init__(color=color, - highlight=highlight, - character=character, - source_player=player, - start_invincible=True, - powerups_expire=powerups_expire) + super().__init__( + color=color, + highlight=highlight, + character=character, + source_player=player, + start_invincible=True, + powerups_expire=powerups_expire, + ) self.last_player_attacked_by: ba.Player | None = None self.last_attacked_time = 0.0 self.last_attacked_type: tuple[str, str] | None = None @@ -77,19 +81,20 @@ class PlayerSpaz(Spaz): # Overloads to tell the type system our return type based on doraise val. @overload - def getplayer(self, - playertype: type[PlayerType], - doraise: Literal[False] = False) -> PlayerType | None: + def getplayer( + self, playertype: type[PlayerType], doraise: Literal[False] = False + ) -> PlayerType | None: ... @overload - def getplayer(self, playertype: type[PlayerType], - doraise: Literal[True]) -> PlayerType: + def getplayer( + self, playertype: type[PlayerType], doraise: Literal[True] + ) -> PlayerType: ... - def getplayer(self, - playertype: type[PlayerType], - doraise: bool = False) -> PlayerType | None: + def getplayer( + self, playertype: type[PlayerType], doraise: bool = False + ) -> PlayerType | None: """Get the ba.Player associated with this Spaz. By default this will return None if the Player no longer exists. @@ -102,13 +107,15 @@ class PlayerSpaz(Spaz): raise ba.PlayerNotFoundError() return player if player.exists() else None - def connect_controls_to_player(self, - enable_jump: bool = True, - enable_punch: bool = True, - enable_pickup: bool = True, - enable_bomb: bool = True, - enable_run: bool = True, - enable_fly: bool = True) -> None: + def connect_controls_to_player( + self, + enable_jump: bool = True, + enable_punch: bool = True, + enable_pickup: bool = True, + enable_bomb: bool = True, + enable_run: bool = True, + enable_fly: bool = True, + ) -> None: """Wire this spaz up to the provided ba.Player. Full control of the character is given by default @@ -129,10 +136,12 @@ class PlayerSpaz(Spaz): player.assigninput(ba.InputType.UP_DOWN, self.on_move_up_down) player.assigninput(ba.InputType.LEFT_RIGHT, self.on_move_left_right) - player.assigninput(ba.InputType.HOLD_POSITION_PRESS, - self.on_hold_position_press) - player.assigninput(ba.InputType.HOLD_POSITION_RELEASE, - self.on_hold_position_release) + player.assigninput( + ba.InputType.HOLD_POSITION_PRESS, self.on_hold_position_press + ) + player.assigninput( + ba.InputType.HOLD_POSITION_RELEASE, self.on_hold_position_release + ) intp = ba.InputType if enable_jump: player.assigninput(intp.JUMP_PRESS, self.on_jump_press) @@ -174,8 +183,10 @@ class PlayerSpaz(Spaz): self.on_run(0.0) self.on_fly_release() else: - print('WARNING: disconnect_controls_from_player() called for' - ' non-connected player') + print( + 'WARNING: disconnect_controls_from_player() called for' + ' non-connected player' + ) def handlemessage(self, msg: Any) -> Any: # FIXME: Tidy this up. @@ -219,8 +230,9 @@ class PlayerSpaz(Spaz): if not self._dead: # Immediate-mode or left-game deaths don't count as 'kills'. - killed = (not msg.immediate - and msg.how is not ba.DeathType.LEFT_GAME) + killed = ( + not msg.immediate and msg.how is not ba.DeathType.LEFT_GAME + ) activity = self._activity() @@ -240,13 +252,16 @@ class PlayerSpaz(Spaz): # all bot kills would register as suicides; need to # change this from last_player_attacked_by to # something like last_actor_attacked_by to fix that. - if (self.last_player_attacked_by - and ba.time() - self.last_attacked_time < 4.0): + if ( + self.last_player_attacked_by + and ba.time() - self.last_attacked_time < 4.0 + ): killerplayer = self.last_player_attacked_by else: # ok, call it a suicide unless we're in co-op - if (activity is not None and not isinstance( - activity.session, ba.CoopSession)): + if activity is not None and not isinstance( + activity.session, ba.CoopSession + ): killerplayer = player else: killerplayer = None @@ -258,8 +273,10 @@ class PlayerSpaz(Spaz): # Only report if both the player and the activity still exist. if killed and activity is not None and player: activity.handlemessage( - ba.PlayerDiedMessage(player, killed, killerplayer, - msg.how)) + ba.PlayerDiedMessage( + player, killed, killerplayer, msg.how + ) + ) super().handlemessage(msg) # Augment standard behavior. diff --git a/dist/ba_data/python/bastd/actor/popuptext.py b/dist/ba_data/python/bastd/actor/popuptext.py index ab270dd..115b359 100644 --- a/dist/ba_data/python/bastd/actor/popuptext.py +++ b/dist/ba_data/python/bastd/actor/popuptext.py @@ -19,13 +19,15 @@ class PopupText(ba.Actor): category: Gameplay Classes """ - def __init__(self, - text: str | ba.Lstr, - position: Sequence[float] = (0.0, 0.0, 0.0), - color: Sequence[float] = (1.0, 1.0, 1.0, 1.0), - random_offset: float = 0.5, - offset: Sequence[float] = (0.0, 0.0, 0.0), - scale: float = 1.0): + def __init__( + self, + text: str | ba.Lstr, + position: Sequence[float] = (0.0, 0.0, 0.0), + color: Sequence[float] = (1.0, 1.0, 1.0, 1.0), + random_offset: float = 0.5, + offset: Sequence[float] = (0.0, 0.0, 0.0), + scale: float = 1.0, + ): """Instantiate with given values. random_offset is the amount of random offset from the provided position @@ -35,73 +37,86 @@ class PopupText(ba.Actor): super().__init__() if len(color) == 3: color = (color[0], color[1], color[2], 1.0) - pos = (position[0] + offset[0] + random_offset * - (0.5 - random.random()), position[1] + offset[1] + - random_offset * (0.5 - random.random()), position[2] + - offset[2] + random_offset * (0.5 - random.random())) + pos = ( + position[0] + offset[0] + random_offset * (0.5 - random.random()), + position[1] + offset[1] + random_offset * (0.5 - random.random()), + position[2] + offset[2] + random_offset * (0.5 - random.random()), + ) - self.node = ba.newnode('text', - attrs={ - 'text': text, - 'in_world': True, - 'shadow': 1.0, - 'flatness': 1.0, - 'h_align': 'center' - }, - delegate=self) + self.node = ba.newnode( + 'text', + attrs={ + 'text': text, + 'in_world': True, + 'shadow': 1.0, + 'flatness': 1.0, + 'h_align': 'center', + }, + delegate=self, + ) lifespan = 1.5 # scale up ba.animate( - self.node, 'scale', { + self.node, + 'scale', + { 0: 0.0, lifespan * 0.11: 0.020 * 0.7 * scale, lifespan * 0.16: 0.013 * 0.7 * scale, - lifespan * 0.25: 0.014 * 0.7 * scale - }) + lifespan * 0.25: 0.014 * 0.7 * scale, + }, + ) # translate upward - self._tcombine = ba.newnode('combine', - owner=self.node, - attrs={ - 'input0': pos[0], - 'input2': pos[2], - 'size': 3 - }) - ba.animate(self._tcombine, 'input1', { - 0: pos[1] + 1.5, - lifespan: pos[1] + 2.0 - }) + self._tcombine = ba.newnode( + 'combine', + owner=self.node, + attrs={'input0': pos[0], 'input2': pos[2], 'size': 3}, + ) + ba.animate( + self._tcombine, 'input1', {0: pos[1] + 1.5, lifespan: pos[1] + 2.0} + ) self._tcombine.connectattr('output', self.node, 'position') # fade our opacity in/out - self._combine = ba.newnode('combine', - owner=self.node, - attrs={ - 'input0': color[0], - 'input1': color[1], - 'input2': color[2], - 'size': 4 - }) + self._combine = ba.newnode( + 'combine', + owner=self.node, + attrs={ + 'input0': color[0], + 'input1': color[1], + 'input2': color[2], + 'size': 4, + }, + ) for i in range(4): ba.animate( - self._combine, 'input' + str(i), { + self._combine, + 'input' + str(i), + { 0.13 * lifespan: color[i], 0.18 * lifespan: 4.0 * color[i], - 0.22 * lifespan: color[i] - }) - ba.animate(self._combine, 'input3', { - 0: 0, - 0.1 * lifespan: color[3], - 0.7 * lifespan: color[3], - lifespan: 0 - }) + 0.22 * lifespan: color[i], + }, + ) + ba.animate( + self._combine, + 'input3', + { + 0: 0, + 0.1 * lifespan: color[3], + 0.7 * lifespan: color[3], + lifespan: 0, + }, + ) self._combine.connectattr('output', self.node, 'color') # kill ourself self._die_timer = ba.Timer( - lifespan, ba.WeakCall(self.handlemessage, ba.DieMessage())) + lifespan, ba.WeakCall(self.handlemessage, ba.DieMessage()) + ) def handlemessage(self, msg: Any) -> Any: assert not self.expired diff --git a/dist/ba_data/python/bastd/actor/powerupbox.py b/dist/ba_data/python/bastd/actor/powerupbox.py index 868667c..adc3115 100644 --- a/dist/ba_data/python/bastd/actor/powerupbox.py +++ b/dist/ba_data/python/bastd/actor/powerupbox.py @@ -87,6 +87,7 @@ class PowerupBoxFactory: to get a shared instance. """ from ba.internal import get_default_powerup_distribution + shared = SharedObjects.get() self._lastpoweruptype: str | None = None self.model = ba.getmodel('powerup') @@ -118,7 +119,8 @@ class PowerupBoxFactory: ('modify_part_collision', 'collide', True), ('modify_part_collision', 'physical', False), ('message', 'our_node', 'at_connect', _TouchedMessage()), - )) + ), + ) # We don't wanna be picked up. self.powerup_material.add_actions( @@ -136,9 +138,11 @@ class PowerupBoxFactory: for _i in range(int(freq)): self._powerupdist.append(powerup) - def get_random_powerup_type(self, - forcetype: str | None = None, - excludetypes: list[str] | None = None) -> str: + def get_random_powerup_type( + self, + forcetype: str | None = None, + excludetypes: list[str] | None = None, + ) -> str: """Returns a random powerup type (string). See ba.Powerup.poweruptype for available type values. @@ -161,9 +165,9 @@ class PowerupBoxFactory: ptype = 'health' else: while True: - ptype = self._powerupdist[random.randint( - 0, - len(self._powerupdist) - 1)] + ptype = self._powerupdist[ + random.randint(0, len(self._powerupdist) - 1) + ] if ptype not in excludetypes: break self._lastpoweruptype = ptype @@ -199,10 +203,12 @@ class PowerupBox(ba.Actor): node: ba.Node """The 'prop' ba.Node representing this box.""" - def __init__(self, - position: Sequence[float] = (0.0, 1.0, 0.0), - poweruptype: str = 'triple_bombs', - expire: bool = True): + def __init__( + self, + position: Sequence[float] = (0.0, 1.0, 0.0), + poweruptype: str = 'triple_bombs', + expire: bool = True, + ): """Create a powerup-box of the requested type at the given position. see ba.Powerup.poweruptype for valid type strings. @@ -250,19 +256,23 @@ class PowerupBox(ba.Actor): 'color_texture': tex, 'reflection': 'powerup', 'reflection_scale': [1.0], - 'materials': (factory.powerup_material, - shared.object_material) - }) # yapf: disable + 'materials': (factory.powerup_material, shared.object_material), + }, + ) # yapf: disable # Animate in. curve = ba.animate(self.node, 'model_scale', {0: 0, 0.14: 1.6, 0.2: 1}) ba.timer(0.2, curve.delete) if expire: - ba.timer(DEFAULT_POWERUP_INTERVAL - 2.5, - ba.WeakCall(self._start_flashing)) - ba.timer(DEFAULT_POWERUP_INTERVAL - 1.0, - ba.WeakCall(self.handlemessage, ba.DieMessage())) + ba.timer( + DEFAULT_POWERUP_INTERVAL - 2.5, + ba.WeakCall(self._start_flashing), + ) + ba.timer( + DEFAULT_POWERUP_INTERVAL - 1.0, + ba.WeakCall(self.handlemessage, ba.DieMessage()), + ) def _start_flashing(self) -> None: if self.node: @@ -275,9 +285,9 @@ class PowerupBox(ba.Actor): factory = PowerupBoxFactory.get() assert self.node if self.poweruptype == 'health': - ba.playsound(factory.health_powerup_sound, - 3, - position=self.node.position) + ba.playsound( + factory.health_powerup_sound, 3, position=self.node.position + ) ba.playsound(factory.powerup_sound, 3, position=self.node.position) self._powersgiven = True self.handlemessage(ba.DieMessage()) @@ -286,7 +296,8 @@ class PowerupBox(ba.Actor): if not self._powersgiven: node = ba.getcollision().opposingnode node.handlemessage( - ba.PowerupMessage(self.poweruptype, sourcenode=self.node)) + ba.PowerupMessage(self.poweruptype, sourcenode=self.node) + ) elif isinstance(msg, ba.DieMessage): if self.node: diff --git a/dist/ba_data/python/bastd/actor/respawnicon.py b/dist/ba_data/python/bastd/actor/respawnicon.py index 66a5cbc..0679522 100644 --- a/dist/ba_data/python/bastd/actor/respawnicon.py +++ b/dist/ba_data/python/bastd/actor/respawnicon.py @@ -39,8 +39,11 @@ class RespawnIcon: # Now find the first unused slot and use that. index = 0 - while (index in respawn_icons and respawn_icons[index]() is not None - and respawn_icons[index]().visible): + while ( + index in respawn_icons + and respawn_icons[index]() is not None + and respawn_icons[index]().visible + ): index += 1 respawn_icons[index] = weakref.ref(self) @@ -50,66 +53,75 @@ class RespawnIcon: h_offs = -10 ipos = (-40 - h_offs if on_right else 40 + h_offs, -180 + offs) self._image: ba.NodeActor | None = ba.NodeActor( - ba.newnode('image', - attrs={ - 'texture': texture, - 'tint_texture': icon['tint_texture'], - 'tint_color': icon['tint_color'], - 'tint2_color': icon['tint2_color'], - 'mask_texture': mask_tex, - 'position': ipos, - 'scale': (32, 32), - 'opacity': 1.0, - 'absolute_scale': True, - 'attach': 'topRight' if on_right else 'topLeft' - })) + ba.newnode( + 'image', + attrs={ + 'texture': texture, + 'tint_texture': icon['tint_texture'], + 'tint_color': icon['tint_color'], + 'tint2_color': icon['tint2_color'], + 'mask_texture': mask_tex, + 'position': ipos, + 'scale': (32, 32), + 'opacity': 1.0, + 'absolute_scale': True, + 'attach': 'topRight' if on_right else 'topLeft', + }, + ) + ) assert self._image.node ba.animate(self._image.node, 'opacity', {0.0: 0, 0.2: 0.7}) npos = (-40 - h_offs if on_right else 40 + h_offs, -205 + 49 + offs) self._name: ba.NodeActor | None = ba.NodeActor( - ba.newnode('text', - attrs={ - 'v_attach': 'top', - 'h_attach': 'right' if on_right else 'left', - 'text': ba.Lstr(value=player.getname()), - 'maxwidth': 100, - 'h_align': 'center', - 'v_align': 'center', - 'shadow': 1.0, - 'flatness': 1.0, - 'color': ba.safecolor(icon['tint_color']), - 'scale': 0.5, - 'position': npos - })) + ba.newnode( + 'text', + attrs={ + 'v_attach': 'top', + 'h_attach': 'right' if on_right else 'left', + 'text': ba.Lstr(value=player.getname()), + 'maxwidth': 100, + 'h_align': 'center', + 'v_align': 'center', + 'shadow': 1.0, + 'flatness': 1.0, + 'color': ba.safecolor(icon['tint_color']), + 'scale': 0.5, + 'position': npos, + }, + ) + ) assert self._name.node ba.animate(self._name.node, 'scale', {0: 0, 0.1: 0.5}) tpos = (-60 - h_offs if on_right else 60 + h_offs, -192 + offs) self._text: ba.NodeActor | None = ba.NodeActor( - ba.newnode('text', - attrs={ - 'position': tpos, - 'h_attach': 'right' if on_right else 'left', - 'h_align': 'right' if on_right else 'left', - 'scale': 0.9, - 'shadow': 0.5, - 'flatness': 0.5, - 'v_attach': 'top', - 'color': ba.safecolor(icon['tint_color']), - 'text': '' - })) + ba.newnode( + 'text', + attrs={ + 'position': tpos, + 'h_attach': 'right' if on_right else 'left', + 'h_align': 'right' if on_right else 'left', + 'scale': 0.9, + 'shadow': 0.5, + 'flatness': 0.5, + 'v_attach': 'top', + 'color': ba.safecolor(icon['tint_color']), + 'text': '', + }, + ) + ) assert self._text.node ba.animate(self._text.node, 'scale', {0: 0, 0.1: 0.9}) self._respawn_time = ba.time() + respawn_time self._update() - self._timer: ba.Timer | None = ba.Timer(1.0, - ba.WeakCall(self._update), - repeat=True) + self._timer: ba.Timer | None = ba.Timer( + 1.0, ba.WeakCall(self._update), repeat=True + ) @property def visible(self) -> bool: diff --git a/dist/ba_data/python/bastd/actor/scoreboard.py b/dist/ba_data/python/bastd/actor/scoreboard.py index d72e883..de4f867 100644 --- a/dist/ba_data/python/bastd/actor/scoreboard.py +++ b/dist/ba_data/python/bastd/actor/scoreboard.py @@ -14,9 +14,15 @@ if TYPE_CHECKING: class _Entry: - - def __init__(self, scoreboard: Scoreboard, team: ba.Team, do_cover: bool, - scale: float, label: ba.Lstr | None, flash_length: float): + def __init__( + self, + scoreboard: Scoreboard, + team: ba.Team, + do_cover: bool, + scale: float, + label: ba.Lstr | None, + flash_length: float, + ): # pylint: disable=too-many-statements self._scoreboard = weakref.ref(scoreboard) self._do_cover = do_cover @@ -45,84 +51,93 @@ class _Entry: if vrmode: self._backing_color = [0.1 + c * 0.1 for c in safe_team_color] else: - self._backing_color = [ - 0.05 + c * 0.17 for c in safe_team_color - ] + self._backing_color = [0.05 + c * 0.17 for c in safe_team_color] else: self._backing_color = [0.05 + c * 0.1 for c in safe_team_color] opacity = (0.8 if vrmode else 0.8) if self._do_cover else 0.5 self._backing = ba.NodeActor( - ba.newnode('image', - attrs={ - 'scale': (self._width, self._height), - 'opacity': opacity, - 'color': self._backing_color, - 'vr_depth': -3, - 'attach': 'topLeft', - 'texture': self._backing_tex - })) + ba.newnode( + 'image', + attrs={ + 'scale': (self._width, self._height), + 'opacity': opacity, + 'color': self._backing_color, + 'vr_depth': -3, + 'attach': 'topLeft', + 'texture': self._backing_tex, + }, + ) + ) self._barcolor = safe_team_color self._bar = ba.NodeActor( - ba.newnode('image', - attrs={ - 'opacity': 0.7, - 'color': self._barcolor, - 'attach': 'topLeft', - 'texture': self._bar_tex - })) + ba.newnode( + 'image', + attrs={ + 'opacity': 0.7, + 'color': self._barcolor, + 'attach': 'topLeft', + 'texture': self._bar_tex, + }, + ) + ) - self._bar_scale = ba.newnode('combine', - owner=self._bar.node, - attrs={ - 'size': 2, - 'input0': self._bar_width, - 'input1': self._bar_height - }) + self._bar_scale = ba.newnode( + 'combine', + owner=self._bar.node, + attrs={ + 'size': 2, + 'input0': self._bar_width, + 'input1': self._bar_height, + }, + ) assert self._bar.node self._bar_scale.connectattr('output', self._bar.node, 'scale') - self._bar_position = ba.newnode('combine', - owner=self._bar.node, - attrs={ - 'size': 2, - 'input0': 0, - 'input1': 0 - }) + self._bar_position = ba.newnode( + 'combine', + owner=self._bar.node, + attrs={'size': 2, 'input0': 0, 'input1': 0}, + ) self._bar_position.connectattr('output', self._bar.node, 'position') self._cover_color = safe_team_color if self._do_cover: self._cover = ba.NodeActor( - ba.newnode('image', - attrs={ - 'scale': - (self._width * 1.15, self._height * 1.6), - 'opacity': 1.0, - 'color': self._cover_color, - 'vr_depth': 2, - 'attach': 'topLeft', - 'texture': self._cover_tex, - 'model_transparent': self._model - })) + ba.newnode( + 'image', + attrs={ + 'scale': (self._width * 1.15, self._height * 1.6), + 'opacity': 1.0, + 'color': self._cover_color, + 'vr_depth': 2, + 'attach': 'topLeft', + 'texture': self._cover_tex, + 'model_transparent': self._model, + }, + ) + ) clr = safe_team_color maxwidth = 130.0 * (1.0 - scoreboard.score_split) - flatness = ((1.0 if vrmode else 0.5) if self._do_cover else 1.0) + flatness = (1.0 if vrmode else 0.5) if self._do_cover else 1.0 self._score_text = ba.NodeActor( - ba.newnode('text', - attrs={ - 'h_attach': 'left', - 'v_attach': 'top', - 'h_align': 'right', - 'v_align': 'center', - 'maxwidth': maxwidth, - 'vr_depth': 2, - 'scale': self._scale * 0.9, - 'text': '', - 'shadow': 1.0 if vrmode else 0.5, - 'flatness': flatness, - 'color': clr - })) + ba.newnode( + 'text', + attrs={ + 'h_attach': 'left', + 'v_attach': 'top', + 'h_align': 'right', + 'v_align': 'center', + 'maxwidth': maxwidth, + 'vr_depth': 2, + 'scale': self._scale * 0.9, + 'text': '', + 'shadow': 1.0 if vrmode else 0.5, + 'flatness': flatness, + 'color': clr, + }, + ) + ) clr = safe_team_color @@ -148,28 +163,31 @@ class _Entry: team_name_label = team_name_label[:10] + '...' team_name_label = ba.Lstr(value=team_name_label) - flatness = ((1.0 if vrmode else 0.5) if self._do_cover else 1.0) + flatness = (1.0 if vrmode else 0.5) if self._do_cover else 1.0 self._name_text = ba.NodeActor( - ba.newnode('text', - attrs={ - 'h_attach': 'left', - 'v_attach': 'top', - 'h_align': 'left', - 'v_align': 'center', - 'vr_depth': 2, - 'scale': self._scale * 0.9, - 'shadow': 1.0 if vrmode else 0.5, - 'flatness': flatness, - 'maxwidth': 130 * scoreboard.score_split, - 'text': team_name_label, - 'color': clr + (1.0, ) - })) + ba.newnode( + 'text', + attrs={ + 'h_attach': 'left', + 'v_attach': 'top', + 'h_align': 'left', + 'v_align': 'center', + 'vr_depth': 2, + 'scale': self._scale * 0.9, + 'shadow': 1.0 if vrmode else 0.5, + 'flatness': flatness, + 'maxwidth': 130 * scoreboard.score_split, + 'text': team_name_label, + 'color': clr + (1.0,), + }, + ) + ) def flash(self, countdown: bool, extra_flash: bool) -> None: """Flash momentarily.""" - self._flash_timer = ba.Timer(0.1, - ba.WeakCall(self._do_flash), - repeat=True) + self._flash_timer = ba.Timer( + 0.1, ba.WeakCall(self._do_flash), repeat=True + ) if countdown: self._flash_counter = 10 else: @@ -186,23 +204,28 @@ class _Entry: return self._pos = tuple(position) - self._backing.node.position = (position[0] + self._width / 2, - position[1] - self._height / 2) + self._backing.node.position = ( + position[0] + self._width / 2, + position[1] - self._height / 2, + ) if self._do_cover: assert self._cover.node - self._cover.node.position = (position[0] + self._width / 2, - position[1] - self._height / 2) + self._cover.node.position = ( + position[0] + self._width / 2, + position[1] - self._height / 2, + ) self._bar_position.input0 = self._pos[0] + self._bar_width / 2 self._bar_position.input1 = self._pos[1] - self._bar_height / 2 assert self._score_text.node - self._score_text.node.position = (self._pos[0] + self._width - - 7.0 * self._scale, - self._pos[1] - self._bar_height + - 16.0 * self._scale) + self._score_text.node.position = ( + self._pos[0] + self._width - 7.0 * self._scale, + self._pos[1] - self._bar_height + 16.0 * self._scale, + ) assert self._name_text.node - self._name_text.node.position = (self._pos[0] + 7.0 * self._scale, - self._pos[1] - self._bar_height + - 16.0 * self._scale) + self._name_text.node.position = ( + self._pos[0] + 7.0 * self._scale, + self._pos[1] - self._bar_height + 16.0 * self._scale, + ) def _set_flash_colors(self, flash: bool) -> None: self._flash_colors = flash @@ -215,16 +238,29 @@ class _Entry: scale = 2.0 _safesetcolor( self._backing.node, - (self._backing_color[0] * scale, self._backing_color[1] * - scale, self._backing_color[2] * scale)) - _safesetcolor(self._bar.node, - (self._barcolor[0] * scale, self._barcolor[1] * - scale, self._barcolor[2] * scale)) + ( + self._backing_color[0] * scale, + self._backing_color[1] * scale, + self._backing_color[2] * scale, + ), + ) + _safesetcolor( + self._bar.node, + ( + self._barcolor[0] * scale, + self._barcolor[1] * scale, + self._barcolor[2] * scale, + ), + ) if self._do_cover: _safesetcolor( self._cover.node, - (self._cover_color[0] * scale, self._cover_color[1] * - scale, self._cover_color[2] * scale)) + ( + self._cover_color[0] * scale, + self._cover_color[1] * scale, + self._cover_color[2] * scale, + ), + ) else: _safesetcolor(self._backing.node, self._backing_color) _safesetcolor(self._bar.node, self._barcolor) @@ -239,12 +275,14 @@ class _Entry: self._flash_counter -= 1 self._set_flash_colors(not self._flash_colors) - def set_value(self, - score: float, - max_score: float | None = None, - countdown: bool = False, - flash: bool = True, - show_value: bool = True) -> None: + def set_value( + self, + score: float, + max_score: float | None = None, + countdown: bool = False, + flash: bool = True, + show_value: bool = True, + ) -> None: """Set the value for the scoreboard entry.""" # If we have no score yet, just set it.. otherwise compare @@ -253,8 +291,11 @@ class _Entry: self._score = score else: if score > self._score or (countdown and score < self._score): - extra_flash = (max_score is not None and score >= max_score - and not countdown) or (countdown and score == 0) + extra_flash = ( + max_score is not None + and score >= max_score + and not countdown + ) or (countdown and score == 0) if flash: self.flash(countdown, extra_flash) self._score = score @@ -265,25 +306,26 @@ class _Entry: if countdown: self._bar_width = max( 2.0 * self._scale, - self._width * (1.0 - (float(score) / max_score))) + self._width * (1.0 - (float(score) / max_score)), + ) else: self._bar_width = max( 2.0 * self._scale, - self._width * (min(1.0, - float(score) / max_score))) + self._width * (min(1.0, float(score) / max_score)), + ) cur_width = self._bar_scale.input0 - ba.animate(self._bar_scale, 'input0', { - 0.0: cur_width, - 0.25: self._bar_width - }) + ba.animate( + self._bar_scale, 'input0', {0.0: cur_width, 0.25: self._bar_width} + ) self._bar_scale.input1 = self._bar_height cur_x = self._bar_position.input0 assert self._pos is not None - ba.animate(self._bar_position, 'input0', { - 0.0: cur_x, - 0.25: self._pos[0] + self._bar_width / 2 - }) + ba.animate( + self._bar_position, + 'input0', + {0.0: cur_x, 0.25: self._pos[0] + self._bar_width / 2}, + ) self._bar_position.input1 = self._pos[1] - self._bar_height / 2 assert self._score_text.node if show_value: @@ -353,13 +395,15 @@ class Scoreboard: self._scale = 1.0 self._flash_length = 1.0 - def set_team_value(self, - team: ba.Team, - score: float, - max_score: float | None = None, - countdown: bool = False, - flash: bool = True, - show_value: bool = True) -> None: + def set_team_value( + self, + team: ba.Team, + score: float, + max_score: float | None = None, + countdown: bool = False, + flash: bool = True, + show_value: bool = True, + ) -> None: """Update the score-board display for the given ba.Team.""" if team.id not in self._entries: self._add_team(team) @@ -370,21 +414,25 @@ class Scoreboard: team.customdata[self._ENTRYSTORENAME] = _EntryProxy(self, team) # Now set the entry. - self._entries[team.id].set_value(score=score, - max_score=max_score, - countdown=countdown, - flash=flash, - show_value=show_value) + self._entries[team.id].set_value( + score=score, + max_score=max_score, + countdown=countdown, + flash=flash, + show_value=show_value, + ) def _add_team(self, team: ba.Team) -> None: if team.id in self._entries: raise RuntimeError('Duplicate team add') - self._entries[team.id] = _Entry(self, - team, - do_cover=self._do_cover, - scale=self._scale, - label=self._label, - flash_length=self._flash_length) + self._entries[team.id] = _Entry( + self, + team, + do_cover=self._do_cover, + scale=self._scale, + label=self._label, + flash_length=self._flash_length, + ) self._update_teams() def remove_team(self, team_id: int) -> None: diff --git a/dist/ba_data/python/bastd/actor/spawner.py b/dist/ba_data/python/bastd/actor/spawner.py index 41e2c33..3d006a2 100644 --- a/dist/ba_data/python/bastd/actor/spawner.py +++ b/dist/ba_data/python/bastd/actor/spawner.py @@ -38,10 +38,10 @@ class Spawner: """The spawn position.""" def __init__( - self, - spawner: Spawner, - data: Any, - pt: Sequence[float], # pylint: disable=invalid-name + self, + spawner: Spawner, + data: Any, + pt: Sequence[float], # pylint: disable=invalid-name ): """Instantiate with the given values.""" self.spawner = spawner @@ -49,12 +49,13 @@ class Spawner: self.pt = pt # pylint: disable=invalid-name def __init__( - self, - data: Any = None, - pt: Sequence[float] = (0, 0, 0), # pylint: disable=invalid-name - spawn_time: float = 1.0, - send_spawn_message: bool = True, - spawn_callback: Callable[[], Any] | None = None): + self, + data: Any = None, + pt: Sequence[float] = (0, 0, 0), # pylint: disable=invalid-name + spawn_time: float = 1.0, + send_spawn_message: bool = True, + spawn_callback: Callable[[], Any] | None = None, + ): """Instantiate a Spawner. Requires some custom data, a position, @@ -66,19 +67,23 @@ class Spawner: self._data = data self._pt = pt # create a light where the spawn will happen - self._light = ba.newnode('light', - attrs={ - 'position': tuple(pt), - 'radius': 0.1, - 'color': (1.0, 0.1, 0.1), - 'lights_volumes': False - }) + self._light = ba.newnode( + 'light', + attrs={ + 'position': tuple(pt), + 'radius': 0.1, + 'color': (1.0, 0.1, 0.1), + 'lights_volumes': False, + }, + ) scl = float(spawn_time) / 3.75 min_val = 0.4 max_val = 0.7 ba.playsound(self._spawner_sound, position=self._light.position) ba.animate( - self._light, 'intensity', { + self._light, + 'intensity', + { 0.0: 0.0, 0.25 * scl: max_val, 0.500 * scl: min_val, @@ -95,8 +100,9 @@ class Spawner: 3.250 * scl: 1.5 * max_val, 3.500 * scl: min_val, 3.750 * scl: 2.0, - 4.000 * scl: 0.0 - }) + 4.000 * scl: 0.0, + }, + ) ba.timer(spawn_time, self._spawn) def _spawn(self) -> None: @@ -108,4 +114,5 @@ class Spawner: activity = ba.getactivity() if activity is not None: activity.handlemessage( - self.SpawnMessage(self, self._data, self._pt)) + self.SpawnMessage(self, self._data, self._pt) + ) diff --git a/dist/ba_data/python/bastd/actor/spaz.py b/dist/ba_data/python/bastd/actor/spaz.py index d227789..4d1529f 100644 --- a/dist/ba_data/python/bastd/actor/spaz.py +++ b/dist/ba_data/python/bastd/actor/spaz.py @@ -63,15 +63,17 @@ class Spaz(ba.Actor): default_boxing_gloves = False default_shields = False - def __init__(self, - color: Sequence[float] = (1.0, 1.0, 1.0), - highlight: Sequence[float] = (0.5, 0.5, 0.5), - character: str = 'Spaz', - source_player: ba.Player | None = None, - start_invincible: bool = True, - can_accept_powerups: bool = True, - powerups_expire: bool = False, - demo_mode: bool = False): + def __init__( + self, + color: Sequence[float] = (1.0, 1.0, 1.0), + highlight: Sequence[float] = (0.5, 0.5, 0.5), + character: str = 'Spaz', + source_player: ba.Player | None = None, + start_invincible: bool = True, + can_accept_powerups: bool = True, + powerups_expire: bool = False, + demo_mode: bool = False, + ): """Create a spaz with the requested color, character, etc.""" # pylint: disable=too-many-statements @@ -104,8 +106,9 @@ class Spaz(ba.Actor): self._cursed = False self._connected_to_player: ba.Player | None = None materials = [ - factory.spaz_material, shared.object_material, - shared.player_material + factory.spaz_material, + shared.object_material, + shared.player_material, ] roller_materials = [factory.roller_material, shared.player_material] extras_material = [] @@ -153,19 +156,18 @@ class Spaz(ba.Actor): 'punch_materials': punchmats, 'pickup_materials': pickupmats, 'invincible': start_invincible, - 'source_player': source_player - }) + 'source_player': source_player, + }, + ) self.shield: ba.Node | None = None if start_invincible: - def _safesetattr(node: ba.Node | None, attr: str, - val: Any) -> None: + def _safesetattr(node: ba.Node | None, attr: str, val: Any) -> None: if node: setattr(node, attr, val) - ba.timer(1.0, ba.Call(_safesetattr, self.node, 'invincible', - False)) + ba.timer(1.0, ba.Call(_safesetattr, self.node, 'invincible', False)) self.hitpoints = 1000 self.hitpoints_max = 1000 self.shield_hitpoints: int | None = None @@ -211,8 +213,7 @@ class Spaz(ba.Actor): self._bomb_held = False if self.default_shields: self.equip_shields() - self._dropped_bomb_callbacks: list[Callable[[Spaz, ba.Actor], - Any]] = [] + self._dropped_bomb_callbacks: list[Callable[[Spaz, ba.Actor], Any]] = [] self._score_text: ba.Node | None = None self._score_text_hide_timer: ba.Timer | None = None @@ -234,7 +235,8 @@ class Spaz(ba.Actor): self.pick_up_powerup_callback = None def add_dropped_bomb_callback( - self, call: Callable[[Spaz, ba.Actor], Any]) -> None: + self, call: Callable[[Spaz, ba.Actor], Any] + ) -> None: """ Add a call to be run whenever this Spaz drops a bomb. The spaz and the newly-dropped bomb are passed as arguments. @@ -251,18 +253,20 @@ class Spaz(ba.Actor): def _hide_score_text(self) -> None: if self._score_text: assert isinstance(self._score_text.scale, float) - ba.animate(self._score_text, 'scale', { - 0.0: self._score_text.scale, - 0.2: 0.0 - }) + ba.animate( + self._score_text, + 'scale', + {0.0: self._score_text.scale, 0.2: 0.0}, + ) def _turbo_filter_add_press(self, source: str) -> None: """ Can pass all button presses through here; if we see an obscene number of them in a short time let's shame/pushish this guy for using turbo """ - t_ms = ba.time(timetype=ba.TimeType.BASE, - timeformat=ba.TimeFormat.MILLISECONDS) + t_ms = ba.time( + timetype=ba.TimeType.BASE, timeformat=ba.TimeFormat.MILLISECONDS + ) assert isinstance(t_ms, int) t_bucket = int(t_ms / 1000) if t_bucket == self._turbo_filter_time_bucket: @@ -270,7 +274,8 @@ class Spaz(ba.Actor): # multiple actions). if t_ms != self._turbo_filter_times.get(source, 0): self._turbo_filter_counts[source] = ( - self._turbo_filter_counts.get(source, 0) + 1) + self._turbo_filter_counts.get(source, 0) + 1 + ) self._turbo_filter_times[source] = t_ms # (uncomment to debug; prints what this count is at) # ba.screenmessage( str(source) + " " @@ -284,23 +289,32 @@ class Spaz(ba.Actor): now = ba.time(ba.TimeType.REAL) if now > ba.app.last_spaz_turbo_warn_time + 30.0: ba.app.last_spaz_turbo_warn_time = now - ba.screenmessage(ba.Lstr( - translate=('statements', - ('Warning to ${NAME}: ' + ba.screenmessage( + ba.Lstr( + translate=( + 'statements', + ( + 'Warning to ${NAME}: ' 'turbo / button-spamming knocks' - ' you out.')), - subs=[('${NAME}', self.node.name)]), - color=(1, 0.5, 0)) + ' you out.' + ), + ), + subs=[('${NAME}', self.node.name)], + ), + color=(1, 0.5, 0), + ) ba.playsound(ba.getsound('error')) else: self._turbo_filter_times = {} self._turbo_filter_time_bucket = t_bucket self._turbo_filter_counts = {source: 1} - def set_score_text(self, - text: str | ba.Lstr, - color: Sequence[float] = (1.0, 1.0, 0.4), - flash: bool = False) -> None: + def set_score_text( + self, + text: str | ba.Lstr, + color: Sequence[float] = (1.0, 1.0, 0.4), + flash: bool = False, + ) -> None: """ Utility func to show a message momentarily over our spaz that follows him around; Handy for score updates and things. @@ -310,24 +324,25 @@ class Spaz(ba.Actor): return if not self._score_text: start_scale = 0.0 - mnode = ba.newnode('math', - owner=self.node, - attrs={ - 'input1': (0, 1.4, 0), - 'operation': 'add' - }) + mnode = ba.newnode( + 'math', + owner=self.node, + attrs={'input1': (0, 1.4, 0), 'operation': 'add'}, + ) self.node.connectattr('torso_position', mnode, 'input2') - self._score_text = ba.newnode('text', - owner=self.node, - attrs={ - 'text': text, - 'in_world': True, - 'shadow': 1.0, - 'flatness': 1.0, - 'color': color_fin, - 'scale': 0.02, - 'h_align': 'center' - }) + self._score_text = ba.newnode( + 'text', + owner=self.node, + attrs={ + 'text': text, + 'in_world': True, + 'shadow': 1.0, + 'flatness': 1.0, + 'color': color_fin, + 'scale': 0.02, + 'h_align': 'center', + }, + ) mnode.connectattr('output', self._score_text, 'position') else: self._score_text.color = color_fin @@ -335,25 +350,26 @@ class Spaz(ba.Actor): start_scale = self._score_text.scale self._score_text.text = text if flash: - combine = ba.newnode('combine', - owner=self._score_text, - attrs={'size': 3}) + combine = ba.newnode( + 'combine', owner=self._score_text, attrs={'size': 3} + ) scl = 1.8 offs = 0.5 tval = 0.300 for i in range(3): cl1 = offs + scl * color_fin[i] cl2 = color_fin[i] - ba.animate(combine, 'input' + str(i), { - 0.5 * tval: cl2, - 0.75 * tval: cl1, - 1.0 * tval: cl2 - }) + ba.animate( + combine, + 'input' + str(i), + {0.5 * tval: cl2, 0.75 * tval: cl1, 1.0 * tval: cl2}, + ) combine.connectattr('output', self._score_text, 'color') ba.animate(self._score_text, 'scale', {0.0: start_scale, 0.2: 0.02}) self._score_text_hide_timer = ba.Timer( - 1.0, ba.WeakCall(self._hide_score_text)) + 1.0, ba.WeakCall(self._hide_score_text) + ) def on_jump_press(self) -> None: """ @@ -438,8 +454,12 @@ class Spaz(ba.Actor): if not self.node.hold_node: ba.timer( 0.1, - ba.WeakCall(self._safe_play_sound, - SpazFactory.get().swish_sound, 0.8)) + ba.WeakCall( + self._safe_play_sound, + SpazFactory.get().swish_sound, + 0.8, + ), + ) self._turbo_filter_add_press('punch') def _safe_play_sound(self, sound: ba.Sound, volume: float) -> None: @@ -585,8 +605,9 @@ class Spaz(ba.Actor): for attr in ['materials', 'roller_materials']: materials = getattr(self.node, attr) if factory.curse_material not in materials: - setattr(self.node, attr, - materials + (factory.curse_material, )) + setattr( + self.node, attr, materials + (factory.curse_material,) + ) # None specifies no time limit assert self.node @@ -596,8 +617,9 @@ class Spaz(ba.Actor): # Note: curse-death-time takes milliseconds. tval = ba.time() assert isinstance(tval, (float, int)) - self.node.curse_death_time = int(1000.0 * - (tval + self.curse_time)) + self.node.curse_death_time = int( + 1000.0 * (tval + self.curse_time) + ) ba.timer(5.0, ba.WeakCall(self.curse_explode)) def equip_boxing_gloves(self) -> None: @@ -626,12 +648,11 @@ class Spaz(ba.Actor): factory = SpazFactory.get() if self.shield is None: - self.shield = ba.newnode('shield', - owner=self.node, - attrs={ - 'color': (0.3, 0.2, 2.0), - 'radius': 1.3 - }) + self.shield = ba.newnode( + 'shield', + owner=self.node, + attrs={'color': (0.3, 0.2, 2.0), 'radius': 1.3}, + ) self.node.connectattr('position_center', self.shield, 'position') self.shield_hitpoints = self.shield_hitpoints_max = 650 self.shield_decay_rate = factory.shield_decay_rate if decay else 0 @@ -639,9 +660,9 @@ class Spaz(ba.Actor): ba.playsound(factory.shield_up_sound, 1.0, position=self.node.position) if self.shield_decay_rate > 0: - self.shield_decay_timer = ba.Timer(0.5, - ba.WeakCall(self.shield_decay), - repeat=True) + self.shield_decay_timer = ba.Timer( + 0.5, ba.WeakCall(self.shield_decay), repeat=True + ) # So user can see the decay. self.shield.always_show_health_bar = True @@ -649,19 +670,23 @@ class Spaz(ba.Actor): """Called repeatedly to decay shield HP over time.""" if self.shield: assert self.shield_hitpoints is not None - self.shield_hitpoints = (max( - 0, self.shield_hitpoints - self.shield_decay_rate)) + self.shield_hitpoints = max( + 0, self.shield_hitpoints - self.shield_decay_rate + ) assert self.shield_hitpoints is not None self.shield.hurt = ( - 1.0 - float(self.shield_hitpoints) / self.shield_hitpoints_max) + 1.0 - float(self.shield_hitpoints) / self.shield_hitpoints_max + ) if self.shield_hitpoints <= 0: self.shield.delete() self.shield = None self.shield_decay_timer = None assert self.node - ba.playsound(SpazFactory.get().shield_down_sound, - 1.0, - position=self.node.position) + ba.playsound( + SpazFactory.get().shield_down_sound, + 1.0, + position=self.node.position, + ) else: self.shield_decay_timer = None @@ -705,15 +730,18 @@ class Spaz(ba.Actor): assert isinstance(t_ms, int) self.node.mini_billboard_1_start_time = t_ms self.node.mini_billboard_1_end_time = ( - t_ms + POWERUP_WEAR_OFF_TIME) - self._multi_bomb_wear_off_flash_timer = (ba.Timer( + t_ms + POWERUP_WEAR_OFF_TIME + ) + self._multi_bomb_wear_off_flash_timer = ba.Timer( (POWERUP_WEAR_OFF_TIME - 2000), ba.WeakCall(self._multi_bomb_wear_off_flash), - timeformat=ba.TimeFormat.MILLISECONDS)) - self._multi_bomb_wear_off_timer = (ba.Timer( + timeformat=ba.TimeFormat.MILLISECONDS, + ) + self._multi_bomb_wear_off_timer = ba.Timer( POWERUP_WEAR_OFF_TIME, ba.WeakCall(self._multi_bomb_wear_off), - timeformat=ba.TimeFormat.MILLISECONDS)) + timeformat=ba.TimeFormat.MILLISECONDS, + ) elif msg.poweruptype == 'land_mines': self.set_land_mine_count(min(self.land_mine_count + 3, 3)) elif msg.poweruptype == 'impact_bombs': @@ -726,15 +754,18 @@ class Spaz(ba.Actor): assert isinstance(t_ms, int) self.node.mini_billboard_2_start_time = t_ms self.node.mini_billboard_2_end_time = ( - t_ms + POWERUP_WEAR_OFF_TIME) - self._bomb_wear_off_flash_timer = (ba.Timer( + t_ms + POWERUP_WEAR_OFF_TIME + ) + self._bomb_wear_off_flash_timer = ba.Timer( POWERUP_WEAR_OFF_TIME - 2000, ba.WeakCall(self._bomb_wear_off_flash), - timeformat=ba.TimeFormat.MILLISECONDS)) - self._bomb_wear_off_timer = (ba.Timer( + timeformat=ba.TimeFormat.MILLISECONDS, + ) + self._bomb_wear_off_timer = ba.Timer( POWERUP_WEAR_OFF_TIME, ba.WeakCall(self._bomb_wear_off), - timeformat=ba.TimeFormat.MILLISECONDS)) + timeformat=ba.TimeFormat.MILLISECONDS, + ) elif msg.poweruptype == 'sticky_bombs': self.bomb_type = 'sticky' tex = self._get_bomb_type_tex() @@ -745,15 +776,18 @@ class Spaz(ba.Actor): assert isinstance(t_ms, int) self.node.mini_billboard_2_start_time = t_ms self.node.mini_billboard_2_end_time = ( - t_ms + POWERUP_WEAR_OFF_TIME) - self._bomb_wear_off_flash_timer = (ba.Timer( + t_ms + POWERUP_WEAR_OFF_TIME + ) + self._bomb_wear_off_flash_timer = ba.Timer( POWERUP_WEAR_OFF_TIME - 2000, ba.WeakCall(self._bomb_wear_off_flash), - timeformat=ba.TimeFormat.MILLISECONDS)) - self._bomb_wear_off_timer = (ba.Timer( + timeformat=ba.TimeFormat.MILLISECONDS, + ) + self._bomb_wear_off_timer = ba.Timer( POWERUP_WEAR_OFF_TIME, ba.WeakCall(self._bomb_wear_off), - timeformat=ba.TimeFormat.MILLISECONDS)) + timeformat=ba.TimeFormat.MILLISECONDS, + ) elif msg.poweruptype == 'punch': tex = PowerupBoxFactory.get().tex_punch self._flash_billboard(tex) @@ -765,15 +799,18 @@ class Spaz(ba.Actor): assert isinstance(t_ms, int) self.node.mini_billboard_3_start_time = t_ms self.node.mini_billboard_3_end_time = ( - t_ms + POWERUP_WEAR_OFF_TIME) - self._boxing_gloves_wear_off_flash_timer = (ba.Timer( + t_ms + POWERUP_WEAR_OFF_TIME + ) + self._boxing_gloves_wear_off_flash_timer = ba.Timer( POWERUP_WEAR_OFF_TIME - 2000, ba.WeakCall(self._gloves_wear_off_flash), - timeformat=ba.TimeFormat.MILLISECONDS)) - self._boxing_gloves_wear_off_timer = (ba.Timer( + timeformat=ba.TimeFormat.MILLISECONDS, + ) + self._boxing_gloves_wear_off_timer = ba.Timer( POWERUP_WEAR_OFF_TIME, ba.WeakCall(self._gloves_wear_off), - timeformat=ba.TimeFormat.MILLISECONDS)) + timeformat=ba.TimeFormat.MILLISECONDS, + ) elif msg.poweruptype == 'shield': factory = SpazFactory.get() @@ -791,15 +828,18 @@ class Spaz(ba.Actor): assert isinstance(t_ms, int) self.node.mini_billboard_2_start_time = t_ms self.node.mini_billboard_2_end_time = ( - t_ms + POWERUP_WEAR_OFF_TIME) - self._bomb_wear_off_flash_timer = (ba.Timer( + t_ms + POWERUP_WEAR_OFF_TIME + ) + self._bomb_wear_off_flash_timer = ba.Timer( POWERUP_WEAR_OFF_TIME - 2000, ba.WeakCall(self._bomb_wear_off_flash), - timeformat=ba.TimeFormat.MILLISECONDS)) - self._bomb_wear_off_timer = (ba.Timer( + timeformat=ba.TimeFormat.MILLISECONDS, + ) + self._bomb_wear_off_timer = ba.Timer( POWERUP_WEAR_OFF_TIME, ba.WeakCall(self._bomb_wear_off), - timeformat=ba.TimeFormat.MILLISECONDS)) + timeformat=ba.TimeFormat.MILLISECONDS, + ) elif msg.poweruptype == 'health': if self._cursed: self._cursed = False @@ -810,9 +850,14 @@ class Spaz(ba.Actor): materials = getattr(self.node, attr) if factory.curse_material in materials: setattr( - self.node, attr, - tuple(m for m in materials - if m != factory.curse_material)) + self.node, + attr, + tuple( + m + for m in materials + if m != factory.curse_material + ), + ) self.node.curse_death_time = 0 self.hitpoints = self.hitpoints_max self._flash_billboard(PowerupBoxFactory.get().tex_health) @@ -829,17 +874,18 @@ class Spaz(ba.Actor): if not self.node: return None if self.node.invincible: - ba.playsound(SpazFactory.get().block_sound, - 1.0, - position=self.node.position) + ba.playsound( + SpazFactory.get().block_sound, + 1.0, + position=self.node.position, + ) return None if self.shield: return None if not self.frozen: self.frozen = True self.node.frozen = True - ba.timer(5.0, ba.WeakCall(self.handlemessage, - ba.ThawMessage())) + ba.timer(5.0, ba.WeakCall(self.handlemessage, ba.ThawMessage())) # Instantly shatter if we're already dead. # (otherwise its hard to tell we're dead) if self.hitpoints <= 0: @@ -854,17 +900,21 @@ class Spaz(ba.Actor): if not self.node: return None if self.node.invincible: - ba.playsound(SpazFactory.get().block_sound, - 1.0, - position=self.node.position) + ba.playsound( + SpazFactory.get().block_sound, + 1.0, + position=self.node.position, + ) return True # If we were recently hit, don't count this as another. # (so punch flurries and bomb pileups essentially count as 1 hit) local_time = ba.time(timeformat=ba.TimeFormat.MILLISECONDS) assert isinstance(local_time, int) - if (self._last_hit_time is None - or local_time - self._last_hit_time > 1000): + if ( + self._last_hit_time is None + or local_time - self._last_hit_time > 1000 + ): self._num_times_hit += 1 self._last_hit_time = local_time @@ -881,17 +931,29 @@ class Spaz(ba.Actor): # theoretical damage; not apply the impulse. assert msg.force_direction is not None self.node.handlemessage( - 'impulse', msg.pos[0], msg.pos[1], msg.pos[2], - msg.velocity[0], msg.velocity[1], msg.velocity[2], mag, - velocity_mag, msg.radius, 1, msg.force_direction[0], - msg.force_direction[1], msg.force_direction[2]) + 'impulse', + msg.pos[0], + msg.pos[1], + msg.pos[2], + msg.velocity[0], + msg.velocity[1], + msg.velocity[2], + mag, + velocity_mag, + msg.radius, + 1, + msg.force_direction[0], + msg.force_direction[1], + msg.force_direction[2], + ) damage = damage_scale * self.node.damage assert self.shield_hitpoints is not None self.shield_hitpoints -= int(damage) self.shield.hurt = ( - 1.0 - - float(self.shield_hitpoints) / self.shield_hitpoints_max) + 1.0 + - float(self.shield_hitpoints) / self.shield_hitpoints_max + ) # Its a cleaner event if a hit just kills the shield # without damaging the player. @@ -903,34 +965,44 @@ class Spaz(ba.Actor): # FIXME: Transition out perhaps? self.shield.delete() self.shield = None - ba.playsound(SpazFactory.get().shield_down_sound, - 1.0, - position=self.node.position) + ba.playsound( + SpazFactory.get().shield_down_sound, + 1.0, + position=self.node.position, + ) # Emit some cool looking sparks when the shield dies. npos = self.node.position - ba.emitfx(position=(npos[0], npos[1] + 0.9, npos[2]), - velocity=self.node.velocity, - count=random.randrange(20, 30), - scale=1.0, - spread=0.6, - chunk_type='spark') + ba.emitfx( + position=(npos[0], npos[1] + 0.9, npos[2]), + velocity=self.node.velocity, + count=random.randrange(20, 30), + scale=1.0, + spread=0.6, + chunk_type='spark', + ) else: - ba.playsound(SpazFactory.get().shield_hit_sound, - 0.5, - position=self.node.position) + ba.playsound( + SpazFactory.get().shield_hit_sound, + 0.5, + position=self.node.position, + ) # Emit some cool looking sparks on shield hit. assert msg.force_direction is not None - ba.emitfx(position=msg.pos, - velocity=(msg.force_direction[0] * 1.0, - msg.force_direction[1] * 1.0, - msg.force_direction[2] * 1.0), - count=min(30, 5 + int(damage * 0.005)), - scale=0.5, - spread=0.3, - chunk_type='spark') + ba.emitfx( + position=msg.pos, + velocity=( + msg.force_direction[0] * 1.0, + msg.force_direction[1] * 1.0, + msg.force_direction[2] * 1.0, + ), + count=min(30, 5 + int(damage * 0.005)), + scale=0.5, + spread=0.3, + chunk_type='spark', + ) # If they passed our spillover threshold, # pass damage along to spaz. @@ -947,16 +1019,28 @@ class Spaz(ba.Actor): shield_leftover_ratio = 1.0 if msg.flat_damage: - damage = int(msg.flat_damage * self.impact_scale * - shield_leftover_ratio) + damage = int( + msg.flat_damage * self.impact_scale * shield_leftover_ratio + ) else: # Hit it with an impulse and get the resulting damage. assert msg.force_direction is not None self.node.handlemessage( - 'impulse', msg.pos[0], msg.pos[1], msg.pos[2], - msg.velocity[0], msg.velocity[1], msg.velocity[2], mag, - velocity_mag, msg.radius, 0, msg.force_direction[0], - msg.force_direction[1], msg.force_direction[2]) + 'impulse', + msg.pos[0], + msg.pos[1], + msg.pos[2], + msg.velocity[0], + msg.velocity[1], + msg.velocity[2], + mag, + velocity_mag, + msg.radius, + 0, + msg.force_direction[0], + msg.force_direction[1], + msg.force_direction[2], + ) damage = int(damage_scale * self.node.damage) self.node.handlemessage('hurt_sound') @@ -968,15 +1052,20 @@ class Spaz(ba.Actor): # If damage was significant, lets show it. if damage >= 350: assert msg.force_direction is not None - ba.show_damage_count('-' + str(int(damage / 10)) + '%', - msg.pos, msg.force_direction) + ba.show_damage_count( + '-' + str(int(damage / 10)) + '%', + msg.pos, + msg.force_direction, + ) # Let's always add in a super-punch sound with boxing # gloves just to differentiate them. if msg.hit_subtype == 'super_punch': - ba.playsound(SpazFactory.get().punch_sound_stronger, - 1.0, - position=self.node.position) + ba.playsound( + SpazFactory.get().punch_sound_stronger, + 1.0, + position=self.node.position, + ) if damage >= 500: sounds = SpazFactory.get().punch_sound_strong sound = sounds[random.randrange(len(sounds))] @@ -988,28 +1077,38 @@ class Spaz(ba.Actor): # Throw up some chunks. assert msg.force_direction is not None - ba.emitfx(position=msg.pos, - velocity=(msg.force_direction[0] * 0.5, - msg.force_direction[1] * 0.5, - msg.force_direction[2] * 0.5), - count=min(10, 1 + int(damage * 0.0025)), - scale=0.3, - spread=0.03) + ba.emitfx( + position=msg.pos, + velocity=( + msg.force_direction[0] * 0.5, + msg.force_direction[1] * 0.5, + msg.force_direction[2] * 0.5, + ), + count=min(10, 1 + int(damage * 0.0025)), + scale=0.3, + spread=0.03, + ) - ba.emitfx(position=msg.pos, - chunk_type='sweat', - velocity=(msg.force_direction[0] * 1.3, - msg.force_direction[1] * 1.3 + 5.0, - msg.force_direction[2] * 1.3), - count=min(30, 1 + int(damage * 0.04)), - scale=0.9, - spread=0.28) + ba.emitfx( + position=msg.pos, + chunk_type='sweat', + velocity=( + msg.force_direction[0] * 1.3, + msg.force_direction[1] * 1.3 + 5.0, + msg.force_direction[2] * 1.3, + ), + count=min(30, 1 + int(damage * 0.04)), + scale=0.9, + spread=0.28, + ) # Momentary flash. hurtiness = damage * 0.003 - punchpos = (msg.pos[0] + msg.force_direction[0] * 0.02, - msg.pos[1] + msg.force_direction[1] * 0.02, - msg.pos[2] + msg.force_direction[2] * 0.02) + punchpos = ( + msg.pos[0] + msg.force_direction[0] * 0.02, + msg.pos[1] + msg.force_direction[1] * 0.02, + msg.pos[2] + msg.force_direction[2] * 0.02, + ) flash_color = (1.0, 0.8, 0.4) light = ba.newnode( 'light', @@ -1018,27 +1117,34 @@ class Spaz(ba.Actor): 'radius': 0.12 + hurtiness * 0.12, 'intensity': 0.3 * (1.0 + 1.0 * hurtiness), 'height_attenuated': False, - 'color': flash_color - }) + 'color': flash_color, + }, + ) ba.timer(0.06, light.delete) - flash = ba.newnode('flash', - attrs={ - 'position': punchpos, - 'size': 0.17 + 0.17 * hurtiness, - 'color': flash_color - }) + flash = ba.newnode( + 'flash', + attrs={ + 'position': punchpos, + 'size': 0.17 + 0.17 * hurtiness, + 'color': flash_color, + }, + ) ba.timer(0.06, flash.delete) if msg.hit_type == 'impact': assert msg.force_direction is not None - ba.emitfx(position=msg.pos, - velocity=(msg.force_direction[0] * 2.0, - msg.force_direction[1] * 2.0, - msg.force_direction[2] * 2.0), - count=min(10, 1 + int(damage * 0.01)), - scale=0.4, - spread=0.1) + ba.emitfx( + position=msg.pos, + velocity=( + msg.force_direction[0] * 2.0, + msg.force_direction[1] * 2.0, + msg.force_direction[2] * 2.0, + ), + count=min(10, 1 + int(damage * 0.01)), + scale=0.4, + spread=0.1, + ) if self.hitpoints > 0: # It's kinda crappy to die from impacts, so lets reduce @@ -1055,22 +1161,26 @@ class Spaz(ba.Actor): if damage > 0.0 and self.node.hold_node: self.node.hold_node = None self.hitpoints -= damage - self.node.hurt = 1.0 - float( - self.hitpoints) / self.hitpoints_max + self.node.hurt = ( + 1.0 - float(self.hitpoints) / self.hitpoints_max + ) # If we're cursed, *any* damage blows us up. if self._cursed and damage > 0: ba.timer( 0.05, - ba.WeakCall(self.curse_explode, - msg.get_source_player(ba.Player))) + ba.WeakCall( + self.curse_explode, msg.get_source_player(ba.Player) + ), + ) # If we're frozen, shatter.. otherwise die if we hit zero if self.frozen and (damage > 200 or self.hitpoints <= 0): self.shatter() elif self.hitpoints <= 0: self.node.handlemessage( - ba.DieMessage(how=ba.DeathType.IMPACT)) + ba.DieMessage(how=ba.DeathType.IMPACT) + ) # If we're dead, take a look at the smoothed damage value # (which gives us a smoothed average of recent damage) and shatter @@ -1102,12 +1212,19 @@ class Spaz(ba.Actor): self.handlemessage(ba.DieMessage(how=ba.DeathType.FALL)) elif isinstance(msg, ba.StandMessage): - self._last_stand_pos = (msg.position[0], msg.position[1], - msg.position[2]) + self._last_stand_pos = ( + msg.position[0], + msg.position[1], + msg.position[2], + ) if self.node: - self.node.handlemessage('stand', msg.position[0], - msg.position[1], msg.position[2], - msg.angle) + self.node.handlemessage( + 'stand', + msg.position[0], + msg.position[1], + msg.position[2], + msg.angle, + ) elif isinstance(msg, CurseExplodeMessage): self.curse_explode() @@ -1120,8 +1237,9 @@ class Spaz(ba.Actor): # Only allow one hit per node per punch. if node and (node not in self._punched_nodes): - punch_momentum_angular = (self.node.punch_momentum_angular * - self._punch_power_scale) + punch_momentum_angular = ( + self.node.punch_momentum_angular * self._punch_power_scale + ) punch_power = self.node.punch_power * self._punch_power_scale # Ok here's the deal: we pass along our base velocity for use @@ -1155,8 +1273,13 @@ class Spaz(ba.Actor): source_player=self.source_player, force_direction=punchdir, hit_type='punch', - hit_subtype=('super_punch' if self._has_boxing_gloves - else 'default'))) + hit_subtype=( + 'super_punch' + if self._has_boxing_gloves + else 'default' + ), + ) + ) # Also apply opposite to ourself for the first punch only. # This is given as a constant force so that it is more @@ -1167,9 +1290,16 @@ class Spaz(ba.Actor): if self._hockey: mag *= 0.5 if len(self._punched_nodes) == 1: - self.node.handlemessage('kick_back', ppos[0], ppos[1], - ppos[2], punchdir[0], punchdir[1], - punchdir[2], mag) + self.node.handlemessage( + 'kick_back', + ppos[0], + ppos[1], + ppos[2], + punchdir[0], + punchdir[1], + punchdir[2], + mag, + ) elif isinstance(msg, PickupMessage): if not self.node: return None @@ -1190,8 +1320,11 @@ class Spaz(ba.Actor): # If we're grabbing the pelvis of a non-shattered spaz, we wanna # grab the torso instead. - if (opposingnode.getnodetype() == 'spaz' - and not opposingnode.shattered and opposingbody == 4): + if ( + opposingnode.getnodetype() == 'spaz' + and not opposingnode.shattered + and opposingbody == 4 + ): opposingbody = 1 # Special case - if we're holding a flag, don't replace it @@ -1233,18 +1366,21 @@ class Spaz(ba.Actor): dropping_bomb = True bomb_type = self.bomb_type - bomb = stdbomb.Bomb(position=(pos[0], pos[1] - 0.0, pos[2]), - velocity=(vel[0], vel[1], vel[2]), - bomb_type=bomb_type, - blast_radius=self.blast_radius, - source_player=self.source_player, - owner=self.node).autoretain() + bomb = stdbomb.Bomb( + position=(pos[0], pos[1] - 0.0, pos[2]), + velocity=(vel[0], vel[1], vel[2]), + bomb_type=bomb_type, + blast_radius=self.blast_radius, + source_player=self.source_player, + owner=self.node, + ).autoretain() assert bomb.node if dropping_bomb: self.bomb_count -= 1 bomb.node.add_death_action( - ba.WeakCall(self.handlemessage, BombDiedMessage())) + ba.WeakCall(self.handlemessage, BombDiedMessage()) + ) self._pick_up(bomb.node) for clb in self._dropped_bomb_callbacks: @@ -1265,7 +1401,8 @@ class Spaz(ba.Actor): if self.land_mine_count != 0: self.node.counter_text = 'x' + str(self.land_mine_count) self.node.counter_texture = ( - PowerupBoxFactory.get().tex_land_mines) + PowerupBoxFactory.get().tex_land_mines + ) else: self.node.counter_text = '' @@ -1281,8 +1418,10 @@ class Spaz(ba.Actor): velocity=self.node.velocity, blast_radius=3.0, blast_type='normal', - source_player=(source_player if source_player else - self.source_player)).autoretain() + source_player=( + source_player if source_player else self.source_player + ), + ).autoretain() self._cursed = False def shatter(self, extreme: bool = False) -> None: @@ -1293,42 +1432,49 @@ class Spaz(ba.Actor): assert self.node if self.frozen: # Momentary flash of light. - light = ba.newnode('light', - attrs={ - 'position': self.node.position, - 'radius': 0.5, - 'height_attenuated': False, - 'color': (0.8, 0.8, 1.0) - }) + light = ba.newnode( + 'light', + attrs={ + 'position': self.node.position, + 'radius': 0.5, + 'height_attenuated': False, + 'color': (0.8, 0.8, 1.0), + }, + ) - ba.animate(light, 'intensity', { - 0.0: 3.0, - 0.04: 0.5, - 0.08: 0.07, - 0.3: 0 - }) + ba.animate( + light, 'intensity', {0.0: 3.0, 0.04: 0.5, 0.08: 0.07, 0.3: 0} + ) ba.timer(0.3, light.delete) # Emit ice chunks. - ba.emitfx(position=self.node.position, - velocity=self.node.velocity, - count=int(random.random() * 10.0 + 10.0), - scale=0.6, - spread=0.2, - chunk_type='ice') - ba.emitfx(position=self.node.position, - velocity=self.node.velocity, - count=int(random.random() * 10.0 + 10.0), - scale=0.3, - spread=0.2, - chunk_type='ice') - ba.playsound(SpazFactory.get().shatter_sound, - 1.0, - position=self.node.position) + ba.emitfx( + position=self.node.position, + velocity=self.node.velocity, + count=int(random.random() * 10.0 + 10.0), + scale=0.6, + spread=0.2, + chunk_type='ice', + ) + ba.emitfx( + position=self.node.position, + velocity=self.node.velocity, + count=int(random.random() * 10.0 + 10.0), + scale=0.3, + spread=0.2, + chunk_type='ice', + ) + ba.playsound( + SpazFactory.get().shatter_sound, + 1.0, + position=self.node.position, + ) else: - ba.playsound(SpazFactory.get().splatter_sound, - 1.0, - position=self.node.position) + ba.playsound( + SpazFactory.get().splatter_sound, + 1.0, + position=self.node.position, + ) self.handlemessage(ba.DieMessage()) self.node.shattered = 2 if extreme else 1 @@ -1337,10 +1483,13 @@ class Spaz(ba.Actor): return pos = self.node.position self.handlemessage( - ba.HitMessage(flat_damage=50.0 * intensity, - pos=pos, - force_direction=self.node.velocity, - hit_type='impact')) + ba.HitMessage( + flat_damage=50.0 * intensity, + pos=pos, + force_direction=self.node.velocity, + hit_type='impact', + ) + ) self.node.handlemessage('knockout', max(0.0, 50.0 * intensity)) sounds: Sequence[ba.Sound] if intensity >= 5.0: @@ -1366,12 +1515,11 @@ class Spaz(ba.Actor): assert self.node self.node.billboard_texture = tex self.node.billboard_cross_out = False - ba.animate(self.node, 'billboard_opacity', { - 0.0: 0.0, - 0.1: 1.0, - 0.4: 1.0, - 0.5: 0.0 - }) + ba.animate( + self.node, + 'billboard_opacity', + {0.0: 0.0, 0.1: 1.0, 0.4: 1.0, 0.5: 0.0}, + ) def set_bomb_count(self, count: int) -> None: """Sets the number of bombs this Spaz has.""" @@ -1398,8 +1546,10 @@ class Spaz(ba.Actor): self._punch_cooldown = factory.punch_cooldown self._has_boxing_gloves = False if self.node: - ba.playsound(PowerupBoxFactory.get().powerdown_sound, - position=self.node.position) + ba.playsound( + PowerupBoxFactory.get().powerdown_sound, + position=self.node.position, + ) self.node.boxing_gloves = False self.node.billboard_opacity = 0.0 @@ -1412,8 +1562,10 @@ class Spaz(ba.Actor): def _multi_bomb_wear_off(self) -> None: self.set_bomb_count(self.default_bomb_count) if self.node: - ba.playsound(PowerupBoxFactory.get().powerdown_sound, - position=self.node.position) + ba.playsound( + PowerupBoxFactory.get().powerdown_sound, + position=self.node.position, + ) self.node.billboard_opacity = 0.0 def _bomb_wear_off_flash(self) -> None: @@ -1425,6 +1577,8 @@ class Spaz(ba.Actor): def _bomb_wear_off(self) -> None: self.bomb_type = self.bomb_type_default if self.node: - ba.playsound(PowerupBoxFactory.get().powerdown_sound, - position=self.node.position) + ba.playsound( + PowerupBoxFactory.get().powerdown_sound, + position=self.node.position, + ) self.node.billboard_opacity = 0.0 diff --git a/dist/ba_data/python/bastd/actor/spazappearance.py b/dist/ba_data/python/bastd/actor/spazappearance.py index 801000e..cdc00d7 100644 --- a/dist/ba_data/python/bastd/actor/spazappearance.py +++ b/dist/ba_data/python/bastd/actor/spazappearance.py @@ -89,8 +89,9 @@ class Appearance: def __init__(self, name: str): self.name = name if self.name in ba.app.spaz_appearances: - raise Exception('spaz appearance name "' + self.name + - '" already exists.') + raise Exception( + 'spaz appearance name "' + self.name + '" already exists.' + ) ba.app.spaz_appearances[self.name] = self self.color_texture = '' self.color_mask_texture = '' @@ -141,10 +142,16 @@ def register_appearances() -> None: t.toes_model = 'neoSpazToes' t.jump_sounds = ['spazJump01', 'spazJump02', 'spazJump03', 'spazJump04'] t.attack_sounds = [ - 'spazAttack01', 'spazAttack02', 'spazAttack03', 'spazAttack04' + 'spazAttack01', + 'spazAttack02', + 'spazAttack03', + 'spazAttack04', ] t.impact_sounds = [ - 'spazImpact01', 'spazImpact02', 'spazImpact03', 'spazImpact04' + 'spazImpact01', + 'spazImpact02', + 'spazImpact03', + 'spazImpact04', ] t.death_sounds = ['spazDeath01'] t.pickup_sounds = ['spazPickup01'] @@ -170,10 +177,16 @@ def register_appearances() -> None: t.toes_model = 'zoeToes' t.jump_sounds = ['zoeJump01', 'zoeJump02', 'zoeJump03'] t.attack_sounds = [ - 'zoeAttack01', 'zoeAttack02', 'zoeAttack03', 'zoeAttack04' + 'zoeAttack01', + 'zoeAttack02', + 'zoeAttack03', + 'zoeAttack04', ] t.impact_sounds = [ - 'zoeImpact01', 'zoeImpact02', 'zoeImpact03', 'zoeImpact04' + 'zoeImpact01', + 'zoeImpact02', + 'zoeImpact03', + 'zoeImpact04', ] t.death_sounds = ['zoeDeath01'] t.pickup_sounds = ['zoePickup01'] @@ -226,8 +239,16 @@ def register_appearances() -> None: t.lower_leg_model = 'kronkLowerLeg' t.toes_model = 'kronkToes' kronk_sounds = [ - 'kronk1', 'kronk2', 'kronk3', 'kronk4', 'kronk5', 'kronk6', 'kronk7', - 'kronk8', 'kronk9', 'kronk10' + 'kronk1', + 'kronk2', + 'kronk3', + 'kronk4', + 'kronk5', + 'kronk6', + 'kronk7', + 'kronk8', + 'kronk9', + 'kronk10', ] t.jump_sounds = kronk_sounds t.attack_sounds = kronk_sounds @@ -255,8 +276,16 @@ def register_appearances() -> None: t.lower_leg_model = 'melLowerLeg' t.toes_model = 'melToes' mel_sounds = [ - 'mel01', 'mel02', 'mel03', 'mel04', 'mel05', 'mel06', 'mel07', 'mel08', - 'mel09', 'mel10' + 'mel01', + 'mel02', + 'mel03', + 'mel04', + 'mel05', + 'mel06', + 'mel07', + 'mel08', + 'mel09', + 'mel10', ] t.attack_sounds = mel_sounds t.jump_sounds = mel_sounds @@ -284,8 +313,13 @@ def register_appearances() -> None: t.lower_leg_model = 'jackLowerLeg' t.toes_model = 'jackToes' hit_sounds = [ - 'jackHit01', 'jackHit02', 'jackHit03', 'jackHit04', 'jackHit05', - 'jackHit06', 'jackHit07' + 'jackHit01', + 'jackHit02', + 'jackHit03', + 'jackHit04', + 'jackHit05', + 'jackHit06', + 'jackHit07', ] sounds = ['jack01', 'jack02', 'jack03', 'jack04', 'jack05', 'jack06'] t.attack_sounds = sounds @@ -340,9 +374,7 @@ def register_appearances() -> None: t.upper_leg_model = 'frostyUpperLeg' t.lower_leg_model = 'frostyLowerLeg' t.toes_model = 'frostyToes' - frosty_sounds = [ - 'frosty01', 'frosty02', 'frosty03', 'frosty04', 'frosty05' - ] + frosty_sounds = ['frosty01', 'frosty02', 'frosty03', 'frosty04', 'frosty05'] frosty_hit_sounds = ['frostyHit01', 'frostyHit02', 'frostyHit03'] t.attack_sounds = frosty_sounds t.jump_sounds = frosty_sounds @@ -558,7 +590,10 @@ def register_appearances() -> None: t.lower_leg_model = 'actionHeroLowerLeg' t.toes_model = 'actionHeroToes' action_hero_sounds = [ - 'actionHero1', 'actionHero2', 'actionHero3', 'actionHero4' + 'actionHero1', + 'actionHero2', + 'actionHero3', + 'actionHero4', ] action_hero_hit_sounds = ['actionHeroHit1', 'actionHeroHit2'] t.attack_sounds = action_hero_sounds @@ -857,7 +892,10 @@ def register_appearances() -> None: t.lower_leg_model = 'operaSingerLowerLeg' t.toes_model = 'operaSingerToes' opera_singer_sounds = [ - 'operaSinger1', 'operaSinger2', 'operaSinger3', 'operaSinger4' + 'operaSinger1', + 'operaSinger2', + 'operaSinger3', + 'operaSinger4', ] opera_singer_hit_sounds = ['operaSingerHit1', 'operaSingerHit2'] t.attack_sounds = opera_singer_sounds diff --git a/dist/ba_data/python/bastd/actor/spazbot.py b/dist/ba_data/python/bastd/actor/spazbot.py index cccf070..9e0c687 100644 --- a/dist/ba_data/python/bastd/actor/spazbot.py +++ b/dist/ba_data/python/bastd/actor/spazbot.py @@ -57,8 +57,12 @@ class SpazBotDiedMessage: how: ba.DeathType """The particular type of death.""" - def __init__(self, spazbot: SpazBot, killerplayer: ba.Player | None, - how: ba.DeathType): + def __init__( + self, + spazbot: SpazBot, + killerplayer: ba.Player | None, + how: ba.DeathType, + ): """Instantiate with given values.""" self.spazbot = spazbot self.killerplayer = killerplayer @@ -105,12 +109,14 @@ class SpazBot(Spaz): def __init__(self) -> None: """Instantiate a spaz-bot.""" - super().__init__(color=self.color, - highlight=self.highlight, - character=self.character, - source_player=None, - start_invincible=False, - can_accept_powerups=False) + super().__init__( + color=self.color, + highlight=self.highlight, + character=self.character, + source_player=None, + start_invincible=False, + can_accept_powerups=False, + ) # If you need to add custom behavior to a bot, set this to a callable # which takes one arg (the bot) and returns False if the bot's normal @@ -126,8 +132,9 @@ class SpazBot(Spaz): self.held_count = 0 self.last_player_held_by: ba.Player | None = None self.target_flag: Flag | None = None - self._charge_speed = 0.5 * (self.charge_speed_min + - self.charge_speed_max) + self._charge_speed = 0.5 * ( + self.charge_speed_min + self.charge_speed_max + ) self._lead_amount = 0.5 self._mode = 'wait' self._charge_closing_in = False @@ -172,16 +179,19 @@ class SpazBot(Spaz): # Ignore player-points that are significantly below the bot # (keeps bots from following players off cliffs). - if (closest_dist is None - or dist < closest_dist) and (plpt[1] > botpt[1] - 5.0): + if (closest_dist is None or dist < closest_dist) and ( + plpt[1] > botpt[1] - 5.0 + ): closest_dist = dist closest_vel = plvel closest = plpt if closest_dist is not None: assert closest_vel is not None assert closest is not None - return (ba.Vec3(closest[0], closest[1], closest[2]), - ba.Vec3(closest_vel[0], closest_vel[1], closest_vel[2])) + return ( + ba.Vec3(closest[0], closest[1], closest[2]), + ba.Vec3(closest_vel[0], closest_vel[1], closest_vel[2]), + ) return None, None def set_player_points(self, pts: list[tuple[ba.Vec3, ba.Vec3]]) -> None: @@ -212,7 +222,7 @@ class SpazBot(Spaz): # towards the flag and try to pick it up. if self.target_flag: if self.node.hold_node: - holding_flag = (self.node.hold_node.getnodetype() == 'flag') + holding_flag = self.node.hold_node.getnodetype() == 'flag' else: holding_flag = False @@ -225,7 +235,7 @@ class SpazBot(Spaz): # Otherwise try to go pick it up. elif self.target_flag.node: target_pt_raw = ba.Vec3(*self.target_flag.node.position) - diff = (target_pt_raw - our_pos) + diff = target_pt_raw - our_pos diff = ba.Vec3(diff[0], 0, diff[2]) # Don't care about y. dist = diff.length() to_target = diff.normalized() @@ -253,8 +263,7 @@ class SpazBot(Spaz): # Not a flag-bearer. If we're holding anything but a bomb, drop it. if self.node.hold_node: - holding_bomb = (self.node.hold_node.getnodetype() - in ['bomb', 'prop']) + holding_bomb = self.node.hold_node.getnodetype() in ['bomb', 'prop'] if not holding_bomb: self.node.pickup_pressed = True self.node.pickup_pressed = False @@ -287,10 +296,11 @@ class SpazBot(Spaz): # Use a point out in front of them as real target. # (more out in front the farther from us they are) - target_pt = (target_pt_raw + - target_vel * dist_raw * 0.3 * self._lead_amount) + target_pt = ( + target_pt_raw + target_vel * dist_raw * 0.3 * self._lead_amount + ) - diff = (target_pt - our_pos) + diff = target_pt - our_pos dist = diff.length() to_target = diff.normalized() @@ -349,8 +359,9 @@ class SpazBot(Spaz): elif self._mode == 'charge': if random.random() < 0.3: - self._charge_speed = random.uniform(self.charge_speed_min, - self.charge_speed_max) + self._charge_speed = random.uniform( + self.charge_speed_min, self.charge_speed_max + ) # If we're a runner we run during charges *except when near # an edge (otherwise we tend to fly off easily). @@ -396,22 +407,29 @@ class SpazBot(Spaz): # from our target. When this value increases it means our charge # is over (ran by them or something). if self._mode == 'charge': - if (self._charge_closing_in - and self._last_charge_dist < dist < 3.0): + if ( + self._charge_closing_in + and self._last_charge_dist < dist < 3.0 + ): self._charge_closing_in = False self._last_charge_dist = dist # If we have a clean shot, throw! - if (self.throw_dist_min <= dist < self.throw_dist_max - and random.random() < self.throwiness and can_attack): + if ( + self.throw_dist_min <= dist < self.throw_dist_max + and random.random() < self.throwiness + and can_attack + ): self._mode = 'throw' - self._lead_amount = ((0.4 + random.random() * 0.6) - if dist_raw > 4.0 else - (0.1 + random.random() * 0.4)) + self._lead_amount = ( + (0.4 + random.random() * 0.6) + if dist_raw > 4.0 + else (0.1 + random.random() * 0.4) + ) self._have_dropped_throw_bomb = False - self._throw_release_time = (ba.time() + - (1.0 / self.throw_rate) * - (0.8 + 1.3 * random.random())) + self._throw_release_time = ba.time() + ( + 1.0 / self.throw_rate + ) * (0.8 + 1.3 * random.random()) # If we're static, always charge (which for us means barely move). elif self.static: @@ -433,8 +451,11 @@ class SpazBot(Spaz): # We're within charging distance, backed against an edge, # or farther than our max throw distance.. chaaarge! - elif (dist < self.charge_dist_max or dist > self.throw_dist_max - or self.map.is_point_near_edge(our_pos, self._running)): + elif ( + dist < self.charge_dist_max + or dist > self.throw_dist_max + or self.map.is_point_near_edge(our_pos, self._running) + ): if self._mode != 'charge': self._mode = 'charge' self._lead_amount = 0.01 @@ -450,10 +471,15 @@ class SpazBot(Spaz): # Do some awesome jumps if we're running. # FIXME: pylint: disable=too-many-boolean-expressions - if ((self._running and 1.2 < dist < 2.2 - and ba.time() - self._last_jump_time > 1.0) - or (self.bouncy and ba.time() - self._last_jump_time > 0.4 - and random.random() < 0.5)): + if ( + self._running + and 1.2 < dist < 2.2 + and ba.time() - self._last_jump_time > 1.0 + ) or ( + self.bouncy + and ba.time() - self._last_jump_time > 0.4 + and random.random() < 0.5 + ): self._last_jump_time = ba.time() self.node.jump_pressed = True self.node.jump_pressed = False @@ -526,8 +552,10 @@ class SpazBot(Spaz): # If they were attacked by someone in the last few # seconds that person's the killer. # Otherwise it was a suicide. - if (self.last_player_attacked_by - and ba.time() - self.last_attacked_time < 4.0): + if ( + self.last_player_attacked_by + and ba.time() - self.last_attacked_time < 4.0 + ): killerplayer = self.last_player_attacked_by else: killerplayer = None @@ -538,7 +566,8 @@ class SpazBot(Spaz): killerplayer = None if activity is not None: activity.handlemessage( - SpazBotDiedMessage(self, killerplayer, msg.how)) + SpazBotDiedMessage(self, killerplayer, msg.how) + ) super().handlemessage(msg) # Augment standard behavior. # Keep track of the player who last hit us for point rewarding. @@ -558,6 +587,7 @@ class BomberBot(SpazBot): category: Bot Classes """ + character = 'Spaz' punchiness = 0.3 @@ -567,6 +597,7 @@ class BomberBotLite(BomberBot): category: Bot Classes """ + color = LITE_BOT_COLOR highlight = LITE_BOT_HIGHLIGHT punchiness = 0.2 @@ -581,6 +612,7 @@ class BomberBotStaticLite(BomberBotLite): category: Bot Classes """ + static = True throw_dist_min = 0.0 @@ -590,6 +622,7 @@ class BomberBotStatic(BomberBot): category: Bot Classes """ + static = True throw_dist_min = 0.0 @@ -599,6 +632,7 @@ class BomberBotPro(BomberBot): category: Bot Classes """ + points_mult = 2 color = PRO_BOT_COLOR highlight = PRO_BOT_HIGHLIGHT @@ -615,6 +649,7 @@ class BomberBotProShielded(BomberBotPro): category: Bot Classes """ + points_mult = 3 default_shields = True @@ -624,6 +659,7 @@ class BomberBotProStatic(BomberBotPro): category: Bot Classes """ + static = True throw_dist_min = 0.0 @@ -633,6 +669,7 @@ class BomberBotProStaticShielded(BomberBotProShielded): category: Bot Classes """ + static = True throw_dist_min = 0.0 @@ -642,6 +679,7 @@ class BrawlerBot(SpazBot): category: Bot Classes """ + character = 'Kronk' punchiness = 0.9 charge_dist_max = 9999.0 @@ -656,6 +694,7 @@ class BrawlerBotLite(BrawlerBot): category: Bot Classes """ + color = LITE_BOT_COLOR highlight = LITE_BOT_HIGHLIGHT punchiness = 0.3 @@ -668,6 +707,7 @@ class BrawlerBotPro(BrawlerBot): category: Bot Classes """ + color = PRO_BOT_COLOR highlight = PRO_BOT_HIGHLIGHT run = True @@ -682,6 +722,7 @@ class BrawlerBotProShielded(BrawlerBotPro): category: Bot Classes """ + default_shields = True points_mult = 3 @@ -731,6 +772,7 @@ class ChargerBotPro(ChargerBot): category: Bot Classes """ + color = PRO_BOT_COLOR highlight = PRO_BOT_HIGHLIGHT default_shields = True @@ -743,6 +785,7 @@ class ChargerBotProShielded(ChargerBotPro): category: Bot Classes """ + default_shields = True points_mult = 4 @@ -752,6 +795,7 @@ class TriggerBot(SpazBot): category: Bot Classes """ + character = 'Zoe' punchiness = 0.75 throwiness = 0.7 @@ -769,6 +813,7 @@ class TriggerBotStatic(TriggerBot): category: Bot Classes """ + static = True throw_dist_min = 0.0 @@ -778,6 +823,7 @@ class TriggerBotPro(TriggerBot): category: Bot Classes """ + color = PRO_BOT_COLOR highlight = PRO_BOT_HIGHLIGHT default_bomb_count = 3 @@ -796,6 +842,7 @@ class TriggerBotProShielded(TriggerBotPro): category: Bot Classes """ + default_shields = True points_mult = 4 @@ -805,6 +852,7 @@ class StickyBot(SpazBot): category: Bot Classes """ + character = 'Mel' punchiness = 0.9 throwiness = 1.0 @@ -826,6 +874,7 @@ class StickyBotStatic(StickyBot): category: Bot Classes """ + static = True @@ -834,6 +883,7 @@ class ExplodeyBot(SpazBot): category: Bot Classes """ + character = 'Jack Morgan' run = True charge_dist_min = 0.0 @@ -851,6 +901,7 @@ class ExplodeyBotNoTimeLimit(ExplodeyBot): category: Bot Classes """ + curse_time = None @@ -859,6 +910,7 @@ class ExplodeyBotShielded(ExplodeyBot): category: Bot Classes """ + default_shields = True points_mult = 5 @@ -889,22 +941,31 @@ class SpazBotSet: self.clear() def spawn_bot( - self, - bot_type: type[SpazBot], - pos: Sequence[float], - spawn_time: float = 3.0, - on_spawn_call: Callable[[SpazBot], Any] | None = None) -> None: + self, + bot_type: type[SpazBot], + pos: Sequence[float], + spawn_time: float = 3.0, + on_spawn_call: Callable[[SpazBot], Any] | None = None, + ) -> None: """Spawn a bot from this set.""" from bastd.actor import spawner - spawner.Spawner(pt=pos, - spawn_time=spawn_time, - send_spawn_message=False, - spawn_callback=ba.Call(self._spawn_bot, bot_type, pos, - on_spawn_call)) + + spawner.Spawner( + pt=pos, + spawn_time=spawn_time, + send_spawn_message=False, + spawn_callback=ba.Call( + self._spawn_bot, bot_type, pos, on_spawn_call + ), + ) self._spawning_count += 1 - def _spawn_bot(self, bot_type: type[SpazBot], pos: Sequence[float], - on_spawn_call: Callable[[SpazBot], Any] | None) -> None: + def _spawn_bot( + self, + bot_type: type[SpazBot], + pos: Sequence[float], + on_spawn_call: Callable[[SpazBot], Any] | None, + ) -> None: spaz = bot_type() ba.playsound(self._spawn_sound, position=pos) assert spaz.node @@ -918,8 +979,9 @@ class SpazBotSet: def have_living_bots(self) -> bool: """Return whether any bots in the set are alive or spawning.""" - return (self._spawning_count > 0 - or any(any(b.is_alive() for b in l) for l in self._bot_lists)) + return self._spawning_count > 0 or any( + any(b.is_alive() for b in l) for l in self._bot_lists + ) def get_living_bots(self) -> list[SpazBot]: """Get the living bots in the set.""" @@ -935,15 +997,18 @@ class SpazBotSet: # Update one of our bot lists each time through. # First off, remove no-longer-existing bots from the list. try: - bot_list = self._bot_lists[self._bot_update_list] = ([ + bot_list = self._bot_lists[self._bot_update_list] = [ b for b in self._bot_lists[self._bot_update_list] if b - ]) + ] except Exception: bot_list = [] - ba.print_exception('Error updating bot list: ' + - str(self._bot_lists[self._bot_update_list])) - self._bot_update_list = (self._bot_update_list + - 1) % self._bot_list_count + ba.print_exception( + 'Error updating bot list: ' + + str(self._bot_lists[self._bot_update_list]) + ) + self._bot_update_list = ( + self._bot_update_list + 1 + ) % self._bot_list_count # Update our list of player points for the bots to use. player_pts = [] @@ -956,8 +1021,12 @@ class SpazBotSet: if player.is_alive(): assert isinstance(player.actor, Spaz) assert player.actor.node - player_pts.append((ba.Vec3(player.actor.node.position), - ba.Vec3(player.actor.node.velocity))) + player_pts.append( + ( + ba.Vec3(player.actor.node.position), + ba.Vec3(player.actor.node.velocity), + ) + ) except Exception: ba.print_exception('Error on bot-set _update.') @@ -980,9 +1049,9 @@ class SpazBotSet: def start_moving(self) -> None: """Start processing bot AI updates so they start doing their thing.""" - self._bot_update_timer = ba.Timer(0.05, - ba.WeakCall(self._update), - repeat=True) + self._bot_update_timer = ba.Timer( + 0.05, ba.WeakCall(self._update), repeat=True + ) def stop_moving(self) -> None: """Tell all bots to stop moving and stops updating their AI. @@ -1022,20 +1091,28 @@ class SpazBotSet: assert bot.node # (should exist if 'if bot' was True) bot.node.move_left_right = 0 bot.node.move_up_down = 0 - ba.timer(0.5 * random.random(), - ba.Call(bot.handlemessage, ba.CelebrateMessage())) + ba.timer( + 0.5 * random.random(), + ba.Call(bot.handlemessage, ba.CelebrateMessage()), + ) jump_duration = random.randrange(400, 500) j = random.randrange(0, 200) for _i in range(10): bot.node.jump_pressed = True bot.node.jump_pressed = False j += jump_duration - ba.timer(random.uniform(0.0, 1.0), - ba.Call(bot.node.handlemessage, 'attack_sound')) - ba.timer(random.uniform(1.0, 2.0), - ba.Call(bot.node.handlemessage, 'attack_sound')) - ba.timer(random.uniform(2.0, 3.0), - ba.Call(bot.node.handlemessage, 'attack_sound')) + ba.timer( + random.uniform(0.0, 1.0), + ba.Call(bot.node.handlemessage, 'attack_sound'), + ) + ba.timer( + random.uniform(1.0, 2.0), + ba.Call(bot.node.handlemessage, 'attack_sound'), + ) + ba.timer( + random.uniform(2.0, 3.0), + ba.Call(bot.node.handlemessage, 'attack_sound'), + ) def add_bot(self, bot: SpazBot) -> None: """Add a ba.SpazBot instance to the set.""" diff --git a/dist/ba_data/python/bastd/actor/spazfactory.py b/dist/ba_data/python/bastd/actor/spazfactory.py index 2611d1c..08cdde6 100644 --- a/dist/ba_data/python/bastd/actor/spazfactory.py +++ b/dist/ba_data/python/bastd/actor/spazfactory.py @@ -89,22 +89,33 @@ class SpazFactory: # pylint: disable=cyclic-import # FIXME: should probably put these somewhere common so we don't # have to import them from a module that imports us. - from bastd.actor.spaz import (PickupMessage, PunchHitMessage, - CurseExplodeMessage) + from bastd.actor.spaz import ( + PickupMessage, + PunchHitMessage, + CurseExplodeMessage, + ) shared = SharedObjects.get() - self.impact_sounds_medium = (ba.getsound('impactMedium'), - ba.getsound('impactMedium2')) - self.impact_sounds_hard = (ba.getsound('impactHard'), - ba.getsound('impactHard2'), - ba.getsound('impactHard3')) - self.impact_sounds_harder = (ba.getsound('bigImpact'), - ba.getsound('bigImpact2')) + self.impact_sounds_medium = ( + ba.getsound('impactMedium'), + ba.getsound('impactMedium2'), + ) + self.impact_sounds_hard = ( + ba.getsound('impactHard'), + ba.getsound('impactHard2'), + ba.getsound('impactHard3'), + ) + self.impact_sounds_harder = ( + ba.getsound('bigImpact'), + ba.getsound('bigImpact2'), + ) self.single_player_death_sound = ba.getsound('playerDeath') self.punch_sound_weak = ba.getsound('punchWeak01') self.punch_sound = ba.getsound('punch01') - self.punch_sound_strong = (ba.getsound('punchStrong01'), - ba.getsound('punchStrong02')) + self.punch_sound_strong = ( + ba.getsound('punchStrong01'), + ba.getsound('punchStrong02'), + ) self.punch_sound_stronger = ba.getsound('superPunch') self.swish_sound = ba.getsound('punchSwish') self.block_sound = ba.getsound('block') @@ -126,47 +137,64 @@ class SpazFactory: # Eww; this probably should just be built into the spaz node. self.roller_material.add_actions( conditions=('they_have_material', footing_material), - actions=(('message', 'our_node', 'at_connect', 'footing', 1), - ('message', 'our_node', 'at_disconnect', 'footing', -1))) + actions=( + ('message', 'our_node', 'at_connect', 'footing', 1), + ('message', 'our_node', 'at_disconnect', 'footing', -1), + ), + ) self.spaz_material.add_actions( conditions=('they_have_material', footing_material), - actions=(('message', 'our_node', 'at_connect', 'footing', 1), - ('message', 'our_node', 'at_disconnect', 'footing', -1))) + actions=( + ('message', 'our_node', 'at_connect', 'footing', 1), + ('message', 'our_node', 'at_disconnect', 'footing', -1), + ), + ) # Punches. self.punch_material.add_actions( - conditions=('they_are_different_node_than_us', ), + conditions=('they_are_different_node_than_us',), actions=( ('modify_part_collision', 'collide', True), ('modify_part_collision', 'physical', False), ('message', 'our_node', 'at_connect', PunchHitMessage()), - )) + ), + ) # Pickups. self.pickup_material.add_actions( - conditions=(('they_are_different_node_than_us', ), 'and', - ('they_have_material', object_material)), + conditions=( + ('they_are_different_node_than_us',), + 'and', + ('they_have_material', object_material), + ), actions=( ('modify_part_collision', 'collide', True), ('modify_part_collision', 'physical', False), ('message', 'our_node', 'at_connect', PickupMessage()), - )) + ), + ) # Curse. self.curse_material.add_actions( conditions=( - ('they_are_different_node_than_us', ), + ('they_are_different_node_than_us',), 'and', ('they_have_material', player_material), ), - actions=('message', 'our_node', 'at_connect', - CurseExplodeMessage()), + actions=( + 'message', + 'our_node', + 'at_connect', + CurseExplodeMessage(), + ), ) - self.foot_impact_sounds = (ba.getsound('footImpact01'), - ba.getsound('footImpact02'), - ba.getsound('footImpact03')) + self.foot_impact_sounds = ( + ba.getsound('footImpact01'), + ba.getsound('footImpact02'), + ba.getsound('footImpact03'), + ) self.foot_skid_sound = ba.getsound('skid01') self.foot_roll_sound = ba.getsound('scamper01') @@ -177,7 +205,8 @@ class SpazFactory: ('impact_sound', self.foot_impact_sounds, 1, 0.2), ('skid_sound', self.foot_skid_sound, 20, 0.3), ('roll_sound', self.foot_roll_sound, 20, 3.0), - )) + ), + ) self.skid_sound = ba.getsound('gravelSkid') @@ -187,7 +216,8 @@ class SpazFactory: ('impact_sound', self.foot_impact_sounds, 20, 6), ('skid_sound', self.skid_sound, 2.0, 1), ('roll_sound', self.skid_sound, 2.0, 1), - )) + ), + ) self.shield_up_sound = ba.getsound('shieldUp') self.shield_down_sound = ba.getsound('shieldDown') @@ -200,7 +230,7 @@ class SpazFactory: ( ('we_are_younger_than', 51), 'and', - ('they_are_different_node_than_us', ), + ('they_are_different_node_than_us',), ), 'and', ('they_dont_have_material', region_material), @@ -213,17 +243,23 @@ class SpazFactory: # Lets load some basic rules. # (allows them to be tweaked from the master server) self.shield_decay_rate = ba.internal.get_v1_account_misc_read_val( - 'rsdr', 10.0) + 'rsdr', 10.0 + ) self.punch_cooldown = ba.internal.get_v1_account_misc_read_val( - 'rpc', 400) - self.punch_cooldown_gloves = (ba.internal.get_v1_account_misc_read_val( - 'rpcg', 300)) + 'rpc', 400 + ) + self.punch_cooldown_gloves = ba.internal.get_v1_account_misc_read_val( + 'rpcg', 300 + ) self.punch_power_scale = ba.internal.get_v1_account_misc_read_val( - 'rpp', 1.2) + 'rpp', 1.2 + ) self.punch_power_scale_gloves = ( - ba.internal.get_v1_account_misc_read_val('rppg', 1.4)) + ba.internal.get_v1_account_misc_read_val('rppg', 1.4) + ) self.max_shield_spillover_damage = ( - ba.internal.get_v1_account_misc_read_val('rsms', 500)) + ba.internal.get_v1_account_misc_read_val('rsms', 500) + ) def get_style(self, character: str) -> str: """Return the named style for this character. @@ -253,7 +289,7 @@ class SpazFactory: 'hand_model': ba.getmodel(char.hand_model), 'upper_leg_model': ba.getmodel(char.upper_leg_model), 'lower_leg_model': ba.getmodel(char.lower_leg_model), - 'toes_model': ba.getmodel(char.toes_model) + 'toes_model': ba.getmodel(char.toes_model), } else: media = self.spaz_media[character] diff --git a/dist/ba_data/python/bastd/actor/text.py b/dist/ba_data/python/bastd/actor/text.py index fc56076..123ac3e 100644 --- a/dist/ba_data/python/bastd/actor/text.py +++ b/dist/ba_data/python/bastd/actor/text.py @@ -18,6 +18,7 @@ class Text(ba.Actor): class Transition(Enum): """Transition types for text.""" + FADE_IN = 'fade_in' IN_RIGHT = 'in_right' IN_LEFT = 'in_left' @@ -27,46 +28,52 @@ class Text(ba.Actor): class HAlign(Enum): """Horizontal alignment type.""" + LEFT = 'left' CENTER = 'center' RIGHT = 'right' class VAlign(Enum): """Vertical alignment type.""" + NONE = 'none' CENTER = 'center' class HAttach(Enum): """Horizontal attach type.""" + LEFT = 'left' CENTER = 'center' RIGHT = 'right' class VAttach(Enum): """Vertical attach type.""" + BOTTOM = 'bottom' CENTER = 'center' TOP = 'top' - def __init__(self, - text: str | ba.Lstr, - position: tuple[float, float] = (0.0, 0.0), - h_align: HAlign = HAlign.LEFT, - v_align: VAlign = VAlign.NONE, - color: Sequence[float] = (1.0, 1.0, 1.0, 1.0), - transition: Transition | None = None, - transition_delay: float = 0.0, - flash: bool = False, - v_attach: VAttach = VAttach.CENTER, - h_attach: HAttach = HAttach.CENTER, - scale: float = 1.0, - transition_out_delay: float | None = None, - maxwidth: float | None = None, - shadow: float = 0.5, - flatness: float = 0.0, - vr_depth: float = 0.0, - host_only: bool = False, - front: bool = False): + def __init__( + self, + text: str | ba.Lstr, + position: tuple[float, float] = (0.0, 0.0), + h_align: HAlign = HAlign.LEFT, + v_align: VAlign = VAlign.NONE, + color: Sequence[float] = (1.0, 1.0, 1.0, 1.0), + transition: Transition | None = None, + transition_delay: float = 0.0, + flash: bool = False, + v_attach: VAttach = VAttach.CENTER, + h_attach: HAttach = HAttach.CENTER, + scale: float = 1.0, + transition_out_delay: float | None = None, + maxwidth: float | None = None, + shadow: float = 0.5, + flatness: float = 0.0, + vr_depth: float = 0.0, + host_only: bool = False, + front: bool = False, + ): # pylint: disable=too-many-statements # pylint: disable=too-many-branches # pylint: disable=too-many-locals @@ -88,21 +95,25 @@ class Text(ba.Actor): 'maxwidth': 0.0 if maxwidth is None else maxwidth, 'host_only': host_only, 'front': front, - 'scale': scale - }) + 'scale': scale, + }, + ) if transition is self.Transition.FADE_IN: if flash: - raise Exception('fixme: flash and fade-in' - ' currently cant both be on') - cmb = ba.newnode('combine', - owner=self.node, - attrs={ - 'input0': color[0], - 'input1': color[1], - 'input2': color[2], - 'size': 4 - }) + raise RuntimeError( + 'fixme: flash and fade-in currently cant both be on' + ) + cmb = ba.newnode( + 'combine', + owner=self.node, + attrs={ + 'input0': color[0], + 'input1': color[1], + 'input2': color[2], + 'size': 4, + }, + ) keys = {transition_delay: 0.0, transition_delay + 0.5: color[3]} if transition_out_delay is not None: keys[transition_delay + transition_out_delay] = color[3] @@ -115,38 +126,35 @@ class Text(ba.Actor): tm1 = 0.15 tm2 = 0.3 cmb = ba.newnode('combine', owner=self.node, attrs={'size': 4}) - ba.animate(cmb, - 'input0', { - 0.0: color[0] * mult, - tm1: color[0], - tm2: color[0] * mult - }, - loop=True) - ba.animate(cmb, - 'input1', { - 0.0: color[1] * mult, - tm1: color[1], - tm2: color[1] * mult - }, - loop=True) - ba.animate(cmb, - 'input2', { - 0.0: color[2] * mult, - tm1: color[2], - tm2: color[2] * mult - }, - loop=True) + ba.animate( + cmb, + 'input0', + {0.0: color[0] * mult, tm1: color[0], tm2: color[0] * mult}, + loop=True, + ) + ba.animate( + cmb, + 'input1', + {0.0: color[1] * mult, tm1: color[1], tm2: color[1] * mult}, + loop=True, + ) + ba.animate( + cmb, + 'input2', + {0.0: color[2] * mult, tm1: color[2], tm2: color[2] * mult}, + loop=True, + ) cmb.input3 = color[3] cmb.connectattr('output', self.node, 'color') - cmb = self.position_combine = ba.newnode('combine', - owner=self.node, - attrs={'size': 2}) + cmb = self.position_combine = ba.newnode( + 'combine', owner=self.node, attrs={'size': 2} + ) if transition is self.Transition.IN_RIGHT: keys = { transition_delay: position[0] + 1300, - transition_delay + 0.2: position[0] + transition_delay + 0.2: position[0], } o_keys = {transition_delay: 0.0, transition_delay + 0.05: 1.0} ba.animate(cmb, 'input0', keys) @@ -155,13 +163,14 @@ class Text(ba.Actor): elif transition is self.Transition.IN_LEFT: keys = { transition_delay: position[0] - 1300, - transition_delay + 0.2: position[0] + transition_delay + 0.2: position[0], } o_keys = {transition_delay: 0.0, transition_delay + 0.05: 1.0} if transition_out_delay is not None: keys[transition_delay + transition_out_delay] = position[0] - keys[transition_delay + transition_out_delay + - 0.2] = position[0] - 1300.0 + keys[transition_delay + transition_out_delay + 0.2] = ( + position[0] - 1300.0 + ) o_keys[transition_delay + transition_out_delay + 0.15] = 1.0 o_keys[transition_delay + transition_out_delay + 0.2] = 0.0 ba.animate(cmb, 'input0', keys) @@ -170,7 +179,7 @@ class Text(ba.Actor): elif transition is self.Transition.IN_BOTTOM_SLOW: keys = { transition_delay: -100.0, - transition_delay + 1.0: position[1] + transition_delay + 1.0: position[1], } o_keys = {transition_delay: 0.0, transition_delay + 0.2: 1.0} cmb.input0 = position[0] @@ -179,7 +188,7 @@ class Text(ba.Actor): elif transition is self.Transition.IN_BOTTOM: keys = { transition_delay: -100.0, - transition_delay + 0.2: position[1] + transition_delay + 0.2: position[1], } o_keys = {transition_delay: 0.0, transition_delay + 0.05: 1.0} if transition_out_delay is not None: @@ -193,7 +202,7 @@ class Text(ba.Actor): elif transition is self.Transition.IN_TOP_SLOW: keys = { transition_delay: 400.0, - transition_delay + 3.5: position[1] + transition_delay + 3.5: position[1], } o_keys = {transition_delay: 0, transition_delay + 1.0: 1.0} cmb.input0 = position[0] @@ -207,8 +216,10 @@ class Text(ba.Actor): # If we're transitioning out, die at the end of it. if transition_out_delay is not None: - ba.timer(transition_delay + transition_out_delay + 1.0, - ba.WeakCall(self.handlemessage, ba.DieMessage())) + ba.timer( + transition_delay + transition_out_delay + 1.0, + ba.WeakCall(self.handlemessage, ba.DieMessage()), + ) def handlemessage(self, msg: Any) -> Any: assert not self.expired diff --git a/dist/ba_data/python/bastd/actor/tipstext.py b/dist/ba_data/python/bastd/actor/tipstext.py index 2cd5afd..f642d95 100644 --- a/dist/ba_data/python/bastd/actor/tipstext.py +++ b/dist/ba_data/python/bastd/actor/tipstext.py @@ -20,44 +20,47 @@ class TipsText(ba.Actor): self._tip_scale = 0.8 self._tip_title_scale = 1.1 self._offs_y = offs_y - self.node = ba.newnode('text', - delegate=self, - attrs={ - 'text': '', - 'scale': self._tip_scale, - 'h_align': 'left', - 'maxwidth': 800, - 'vr_depth': -20, - 'v_align': 'center', - 'v_attach': 'bottom' - }) - tval = ba.Lstr(value='${A}:', - subs=[('${A}', ba.Lstr(resource='tipText'))]) - self.title_node = ba.newnode('text', - delegate=self, - attrs={ - 'text': tval, - 'scale': self._tip_title_scale, - 'maxwidth': 122, - 'h_align': 'right', - 'vr_depth': -20, - 'v_align': 'center', - 'v_attach': 'bottom' - }) + self.node = ba.newnode( + 'text', + delegate=self, + attrs={ + 'text': '', + 'scale': self._tip_scale, + 'h_align': 'left', + 'maxwidth': 800, + 'vr_depth': -20, + 'v_align': 'center', + 'v_attach': 'bottom', + }, + ) + tval = ba.Lstr( + value='${A}:', subs=[('${A}', ba.Lstr(resource='tipText'))] + ) + self.title_node = ba.newnode( + 'text', + delegate=self, + attrs={ + 'text': tval, + 'scale': self._tip_title_scale, + 'maxwidth': 122, + 'h_align': 'right', + 'vr_depth': -20, + 'v_align': 'center', + 'v_attach': 'bottom', + }, + ) self._message_duration = 10000 self._message_spacing = 3000 self._change_timer = ba.Timer( 0.001 * (self._message_duration + self._message_spacing), ba.WeakCall(self.change_phrase), - repeat=True) - self._combine = ba.newnode('combine', - owner=self.node, - attrs={ - 'input0': 1.0, - 'input1': 0.8, - 'input2': 1.0, - 'size': 4 - }) + repeat=True, + ) + self._combine = ba.newnode( + 'combine', + owner=self.node, + attrs={'input0': 1.0, 'input1': 0.8, 'input2': 1.0, 'size': 4}, + ) self._combine.connectattr('output', self.node, 'color') self._combine.connectattr('output', self.title_node, 'color') self.change_phrase() @@ -65,9 +68,11 @@ class TipsText(ba.Actor): def change_phrase(self) -> None: """Switch the visible tip phrase.""" from ba.internal import get_remote_app_name, get_next_tip - next_tip = ba.Lstr(translate=('tips', get_next_tip()), - subs=[('${REMOTE_APP_NAME}', get_remote_app_name()) - ]) + + next_tip = ba.Lstr( + translate=('tips', get_next_tip()), + subs=[('${REMOTE_APP_NAME}', get_remote_app_name())], + ) spc = self._message_spacing assert self.node self.node.position = (-200, self._offs_y) @@ -76,12 +81,14 @@ class TipsText(ba.Actor): spc: 0, spc + 1000: 1.0, spc + self._message_duration - 1000: 1.0, - spc + self._message_duration: 0.0 + spc + self._message_duration: 0.0, } - ba.animate(self._combine, - 'input3', {k: v * 0.5 - for k, v in list(keys.items())}, - timeformat=ba.TimeFormat.MILLISECONDS) + ba.animate( + self._combine, + 'input3', + {k: v * 0.5 for k, v in list(keys.items())}, + timeformat=ba.TimeFormat.MILLISECONDS, + ) self.node.text = next_tip def handlemessage(self, msg: Any) -> Any: diff --git a/dist/ba_data/python/bastd/actor/zoomtext.py b/dist/ba_data/python/bastd/actor/zoomtext.py index 708d943..2cca306 100644 --- a/dist/ba_data/python/bastd/actor/zoomtext.py +++ b/dist/ba_data/python/bastd/actor/zoomtext.py @@ -21,22 +21,24 @@ class ZoomText(ba.Actor): Used for things such as the 'BOB WINS' victory messages. """ - def __init__(self, - text: str | ba.Lstr, - position: tuple[float, float] = (0.0, 0.0), - shiftposition: tuple[float, float] | None = None, - shiftdelay: float | None = None, - lifespan: float | None = None, - flash: bool = True, - trail: bool = True, - h_align: str = 'center', - color: Sequence[float] = (0.9, 0.4, 0.0), - jitter: float = 0.0, - trailcolor: Sequence[float] = (1.0, 0.35, 0.1, 0.0), - scale: float = 1.0, - project_scale: float = 1.0, - tilt_translate: float = 0.0, - maxwidth: float | None = None): + def __init__( + self, + text: str | ba.Lstr, + position: tuple[float, float] = (0.0, 0.0), + shiftposition: tuple[float, float] | None = None, + shiftdelay: float | None = None, + lifespan: float | None = None, + flash: bool = True, + trail: bool = True, + h_align: str = 'center', + color: Sequence[float] = (0.9, 0.4, 0.0), + jitter: float = 0.0, + trailcolor: Sequence[float] = (1.0, 0.35, 0.1, 0.0), + scale: float = 1.0, + project_scale: float = 1.0, + tilt_translate: float = 0.0, + maxwidth: float | None = None, + ): # pylint: disable=too-many-locals super().__init__() self._dying = False @@ -61,8 +63,9 @@ class ZoomText(ba.Actor): 'maxwidth': maxwidth if maxwidth is not None else 0.0, 'tilt_translate': tilt_translate, 'h_align': h_align, - 'v_align': 'center' - }) + 'v_align': 'center', + }, + ) # we never jitter in vr mode.. if ba.app.vr_mode: @@ -78,79 +81,81 @@ class ZoomText(ba.Actor): positionadjusted2 = (shiftposition[0], shiftposition[1] - 100) ba.timer( shiftdelay, - ba.WeakCall(self._shift, positionadjusted, positionadjusted2)) + ba.WeakCall(self._shift, positionadjusted, positionadjusted2), + ) if jitter > 0.0: ba.timer( shiftdelay + 0.25, - ba.WeakCall(self._jitter, positionadjusted2, - jitter * scale)) - color_combine = ba.newnode('combine', - owner=self.node, - attrs={ - 'input2': color[2], - 'input3': 1.0, - 'size': 4 - }) + ba.WeakCall( + self._jitter, positionadjusted2, jitter * scale + ), + ) + color_combine = ba.newnode( + 'combine', + owner=self.node, + attrs={'input2': color[2], 'input3': 1.0, 'size': 4}, + ) if trail: - trailcolor_n = ba.newnode('combine', - owner=self.node, - attrs={ - 'size': 3, - 'input0': trailcolor[0], - 'input1': trailcolor[1], - 'input2': trailcolor[2] - }) + trailcolor_n = ba.newnode( + 'combine', + owner=self.node, + attrs={ + 'size': 3, + 'input0': trailcolor[0], + 'input1': trailcolor[1], + 'input2': trailcolor[2], + }, + ) trailcolor_n.connectattr('output', self.node, 'trailcolor') basemult = 0.85 ba.animate( - self.node, 'trail_project_scale', { + self.node, + 'trail_project_scale', + { 0: 0 * project_scale, basemult * 0.201: 0.6 * project_scale, basemult * 0.347: 0.8 * project_scale, basemult * 0.478: 0.9 * project_scale, basemult * 0.595: 0.93 * project_scale, basemult * 0.748: 0.95 * project_scale, - basemult * 0.941: 0.95 * project_scale - }) + basemult * 0.941: 0.95 * project_scale, + }, + ) if flash: mult = 2.0 tm1 = 0.15 tm2 = 0.3 - ba.animate(color_combine, - 'input0', { - 0: color[0] * mult, - tm1: color[0], - tm2: color[0] * mult - }, - loop=True) - ba.animate(color_combine, - 'input1', { - 0: color[1] * mult, - tm1: color[1], - tm2: color[1] * mult - }, - loop=True) - ba.animate(color_combine, - 'input2', { - 0: color[2] * mult, - tm1: color[2], - tm2: color[2] * mult - }, - loop=True) + ba.animate( + color_combine, + 'input0', + {0: color[0] * mult, tm1: color[0], tm2: color[0] * mult}, + loop=True, + ) + ba.animate( + color_combine, + 'input1', + {0: color[1] * mult, tm1: color[1], tm2: color[1] * mult}, + loop=True, + ) + ba.animate( + color_combine, + 'input2', + {0: color[2] * mult, tm1: color[2], tm2: color[2] * mult}, + loop=True, + ) else: color_combine.input0 = color[0] color_combine.input1 = color[1] color_combine.connectattr('output', self.node, 'color') - ba.animate(self.node, 'project_scale', { - 0: 0, - 0.27: 1.05 * project_scale, - 0.3: 1 * project_scale - }) + ba.animate( + self.node, + 'project_scale', + {0: 0, 0.27: 1.05 * project_scale, 0.3: 1 * project_scale}, + ) # if they give us a lifespan, kill ourself down the line if lifespan is not None: - ba.timer(lifespan, ba.WeakCall(self.handlemessage, - ba.DieMessage())) + ba.timer(lifespan, ba.WeakCall(self.handlemessage, ba.DieMessage())) def handlemessage(self, msg: Any) -> Any: assert not self.expired @@ -161,18 +166,22 @@ class ZoomText(ba.Actor): self.node.delete() else: ba.animate( - self.node, 'project_scale', { + self.node, + 'project_scale', + { 0.0: 1 * self._project_scale, - 0.6: 1.2 * self._project_scale - }) + 0.6: 1.2 * self._project_scale, + }, + ) ba.animate(self.node, 'opacity', {0.0: 1, 0.3: 0}) ba.animate(self.node, 'trail_opacity', {0.0: 1, 0.6: 0}) ba.timer(0.7, self.node.delete) return None return super().handlemessage(msg) - def _jitter(self, position: tuple[float, float], - jitter_amount: float) -> None: + def _jitter( + self, position: tuple[float, float], jitter_amount: float + ) -> None: if not self.node: return cmb = ba.newnode('combine', owner=self.node, attrs={'size': 2}) @@ -181,14 +190,17 @@ class ZoomText(ba.Actor): timeval = 0.0 # gen some random keys for that stop-motion-y look for _i in range(10): - keys[timeval] = (position[index] + - (random.random() - 0.5) * jitter_amount * 1.6) + keys[timeval] = ( + position[index] + + (random.random() - 0.5) * jitter_amount * 1.6 + ) timeval += random.random() * 0.1 ba.animate(cmb, attr, keys, loop=True) cmb.connectattr('output', self.node, 'position') - def _shift(self, position1: tuple[float, float], - position2: tuple[float, float]) -> None: + def _shift( + self, position1: tuple[float, float], position2: tuple[float, float] + ) -> None: if not self.node: return cmb = ba.newnode('combine', owner=self.node, attrs={'size': 2}) diff --git a/dist/ba_data/python/bastd/appdelegate.py b/dist/ba_data/python/bastd/appdelegate.py index 464117c..746aa5d 100644 --- a/dist/ba_data/python/bastd/appdelegate.py +++ b/dist/ba_data/python/bastd/appdelegate.py @@ -15,17 +15,23 @@ class AppDelegate(ba.AppDelegate): """Defines handlers for high level app functionality.""" def create_default_game_settings_ui( - self, gameclass: type[ba.GameActivity], - sessiontype: type[ba.Session], settings: dict | None, - completion_call: Callable[[dict | None], Any]) -> None: + self, + gameclass: type[ba.GameActivity], + sessiontype: type[ba.Session], + settings: dict | None, + completion_call: Callable[[dict | None], Any], + ) -> None: """(internal)""" # Replace the main window once we come up successfully. from bastd.ui.playlist.editgame import PlaylistEditGameWindow + ba.app.ui.clear_main_menu_window(transition='out_left') ba.app.ui.set_main_menu_window( PlaylistEditGameWindow( gameclass, sessiontype, settings, - completion_call=completion_call).get_root_widget()) + completion_call=completion_call, + ).get_root_widget() + ) diff --git a/dist/ba_data/python/bastd/game/assault.py b/dist/ba_data/python/bastd/game/assault.py index e9f4ff1..a2feb11 100644 --- a/dist/ba_data/python/bastd/game/assault.py +++ b/dist/ba_data/python/bastd/game/assault.py @@ -91,8 +91,9 @@ class AssaultGame(ba.TeamGameActivity[Player, Team]): # Base class overrides self.slow_motion = self._epic_mode - self.default_music = (ba.MusicType.EPIC if self._epic_mode else - ba.MusicType.FORWARD_MARCH) + self.default_music = ( + ba.MusicType.EPIC if self._epic_mode else ba.MusicType.FORWARD_MARCH + ) def get_instance_description(self) -> str | Sequence: if self._score_to_win == 1: @@ -107,19 +108,19 @@ class AssaultGame(ba.TeamGameActivity[Player, Team]): def create_team(self, sessionteam: ba.SessionTeam) -> Team: shared = SharedObjects.get() base_pos = self.map.get_flag_position(sessionteam.id) - ba.newnode('light', - attrs={ - 'position': base_pos, - 'intensity': 0.6, - 'height_attenuated': False, - 'volume_intensity_scale': 0.1, - 'radius': 0.1, - 'color': sessionteam.color - }) + ba.newnode( + 'light', + attrs={ + 'position': base_pos, + 'intensity': 0.6, + 'height_attenuated': False, + 'volume_intensity_scale': 0.1, + 'radius': 0.1, + 'color': sessionteam.color, + }, + ) Flag.project_stand(base_pos) - flag = Flag(touchable=False, - position=base_pos, - color=sessionteam.color) + flag = Flag(touchable=False, position=base_pos, color=sessionteam.color) team = Team(base_pos=base_pos, flag=flag) mat = self._base_region_materials[sessionteam.id] = ba.Material() @@ -128,8 +129,11 @@ class AssaultGame(ba.TeamGameActivity[Player, Team]): actions=( ('modify_part_collision', 'collide', True), ('modify_part_collision', 'physical', False), - ('call', 'at_connect', ba.Call(self._handle_base_collide, - team)), + ( + 'call', + 'at_connect', + ba.Call(self._handle_base_collide, team), + ), ), ) @@ -140,8 +144,9 @@ class AssaultGame(ba.TeamGameActivity[Player, Team]): 'position': (base_pos[0], base_pos[1] + 0.75, base_pos[2]), 'scale': (0.5, 0.5, 0.5), 'type': 'sphere', - 'materials': [self._base_region_materials[sessionteam.id]] - }) + 'materials': [self._base_region_materials[sessionteam.id]], + }, + ) return team @@ -163,13 +168,15 @@ class AssaultGame(ba.TeamGameActivity[Player, Team]): super().handlemessage(msg) def _flash_base(self, team: Team, length: float = 2.0) -> None: - light = ba.newnode('light', - attrs={ - 'position': team.base_pos, - 'height_attenuated': False, - 'radius': 0.3, - 'color': team.color - }) + light = ba.newnode( + 'light', + attrs={ + 'position': team.base_pos, + 'height_attenuated': False, + 'radius': 0.3, + 'color': team.color, + }, + ) ba.animate(light, 'intensity', {0: 0, 0.25: 2.0, 0.5: 0}, loop=True) ba.timer(length, light.delete) @@ -203,38 +210,34 @@ class AssaultGame(ba.TeamGameActivity[Player, Team]): for player in player_team.players: if player.is_alive(): pos = player.node.position - light = ba.newnode('light', - attrs={ - 'position': pos, - 'color': player_team.color, - 'height_attenuated': False, - 'radius': 0.4 - }) + light = ba.newnode( + 'light', + attrs={ + 'position': pos, + 'color': player_team.color, + 'height_attenuated': False, + 'radius': 0.4, + }, + ) ba.timer(0.5, light.delete) - ba.animate(light, 'intensity', { - 0: 0, - 0.1: 1.0, - 0.5: 0 - }) + ba.animate(light, 'intensity', {0: 0, 0.1: 1.0, 0.5: 0}) - new_pos = (self.map.get_start_position(player_team.id)) - light = ba.newnode('light', - attrs={ - 'position': new_pos, - 'color': player_team.color, - 'radius': 0.4, - 'height_attenuated': False - }) + new_pos = self.map.get_start_position(player_team.id) + light = ba.newnode( + 'light', + attrs={ + 'position': new_pos, + 'color': player_team.color, + 'radius': 0.4, + 'height_attenuated': False, + }, + ) ba.timer(0.5, light.delete) - ba.animate(light, 'intensity', { - 0: 0, - 0.1: 1.0, - 0.5: 0 - }) + ba.animate(light, 'intensity', {0: 0, 0.1: 1.0, 0.5: 0}) if player.actor: player.actor.handlemessage( - ba.StandMessage(new_pos, - random.uniform(0, 360))) + ba.StandMessage(new_pos, random.uniform(0, 360)) + ) # Have teammates celebrate. for player in player_team.players: @@ -254,5 +257,6 @@ class AssaultGame(ba.TeamGameActivity[Player, Team]): def _update_scoreboard(self) -> None: for team in self.teams: - self._scoreboard.set_team_value(team, team.score, - self._score_to_win) + self._scoreboard.set_team_value( + team, team.score, self._score_to_win + ) diff --git a/dist/ba_data/python/bastd/game/capturetheflag.py b/dist/ba_data/python/bastd/game/capturetheflag.py index de3fb7e..bb8ad9c 100644 --- a/dist/ba_data/python/bastd/game/capturetheflag.py +++ b/dist/ba_data/python/bastd/game/capturetheflag.py @@ -12,8 +12,13 @@ from typing import TYPE_CHECKING import ba from bastd.actor.playerspaz import PlayerSpaz from bastd.actor.scoreboard import Scoreboard -from bastd.actor.flag import (FlagFactory, Flag, FlagPickedUpMessage, - FlagDroppedMessage, FlagDiedMessage) +from bastd.actor.flag import ( + FlagFactory, + Flag, + FlagPickedUpMessage, + FlagDroppedMessage, + FlagDiedMessage, +) if TYPE_CHECKING: from typing import Any, Sequence @@ -26,18 +31,18 @@ class CTFFlag(Flag): def __init__(self, team: Team): assert team.flagmaterial is not None - super().__init__(materials=[team.flagmaterial], - position=team.base_pos, - color=team.color) + super().__init__( + materials=[team.flagmaterial], + position=team.base_pos, + color=team.color, + ) self._team = team self.held_count = 0 - self.counter = ba.newnode('text', - owner=self.node, - attrs={ - 'in_world': True, - 'scale': 0.02, - 'h_align': 'center' - }) + self.counter = ba.newnode( + 'text', + owner=self.node, + attrs={'in_world': True, 'scale': 0.02, 'h_align': 'center'}, + ) self.reset_return_times() self.last_player_to_hold: Player | None = None self.time_out_respawn_time: int | None = None @@ -64,11 +69,15 @@ class Player(ba.Player['Team']): class Team(ba.Team[Player]): """Our team type for this game.""" - def __init__(self, base_pos: Sequence[float], - base_region_material: ba.Material, base_region: ba.Node, - spaz_material_no_flag_physical: ba.Material, - spaz_material_no_flag_collide: ba.Material, - flagmaterial: ba.Material): + def __init__( + self, + base_pos: Sequence[float], + base_region_material: ba.Material, + base_region: ba.Node, + spaz_material_no_flag_physical: ba.Material, + spaz_material_no_flag_collide: ba.Material, + flagmaterial: ba.Material, + ): self.base_pos = base_pos self.base_region_material = base_region_material self.base_region = base_region @@ -158,8 +167,9 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): # Base class overrides. self.slow_motion = self._epic_mode - self.default_music = (ba.MusicType.EPIC if self._epic_mode else - ba.MusicType.FLAG_CATCHER) + self.default_music = ( + ba.MusicType.EPIC if self._epic_mode else ba.MusicType.FLAG_CATCHER + ) def get_instance_description(self) -> str | Sequence: if self._score_to_win == 1: @@ -178,15 +188,17 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): base_pos = self.map.get_flag_position(sessionteam.id) Flag.project_stand(base_pos) - ba.newnode('light', - attrs={ - 'position': base_pos, - 'intensity': 0.6, - 'height_attenuated': False, - 'volume_intensity_scale': 0.1, - 'radius': 0.1, - 'color': sessionteam.color - }) + ba.newnode( + 'light', + attrs={ + 'position': base_pos, + 'intensity': 0.6, + 'height_attenuated': False, + 'volume_intensity_scale': 0.1, + 'radius': 0.1, + 'color': sessionteam.color, + }, + ) base_region_mat = ba.Material() pos = base_pos @@ -196,19 +208,22 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): 'position': (pos[0], pos[1] + 0.75, pos[2]), 'scale': (0.5, 0.5, 0.5), 'type': 'sphere', - 'materials': [base_region_mat, self._all_bases_material] - }) + 'materials': [base_region_mat, self._all_bases_material], + }, + ) spaz_mat_no_flag_physical = ba.Material() spaz_mat_no_flag_collide = ba.Material() flagmat = ba.Material() - team = Team(base_pos=base_pos, - base_region_material=base_region_mat, - base_region=base_region, - spaz_material_no_flag_physical=spaz_mat_no_flag_physical, - spaz_material_no_flag_collide=spaz_mat_no_flag_collide, - flagmaterial=flagmat) + team = Team( + base_pos=base_pos, + base_region_material=base_region_mat, + base_region=base_region, + spaz_material_no_flag_physical=spaz_mat_no_flag_physical, + spaz_material_no_flag_collide=spaz_mat_no_flag_collide, + flagmaterial=flagmat, + ) # Some parts of our spazzes don't collide physically with our # flags but generate callbacks. @@ -216,11 +231,18 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): conditions=('they_have_material', flagmat), actions=( ('modify_part_collision', 'physical', False), - ('call', 'at_connect', - lambda: self._handle_touching_own_flag(team, True)), - ('call', 'at_disconnect', - lambda: self._handle_touching_own_flag(team, False)), - )) + ( + 'call', + 'at_connect', + lambda: self._handle_touching_own_flag(team, True), + ), + ( + 'call', + 'at_disconnect', + lambda: self._handle_touching_own_flag(team, False), + ), + ), + ) # Other parts of our spazzes don't collide with our flags at all. spaz_mat_no_flag_collide.add_actions( @@ -234,11 +256,18 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): actions=( ('modify_part_collision', 'collide', True), ('modify_part_collision', 'physical', False), - ('call', 'at_connect', - lambda: self._handle_flag_entered_base(team)), - ('call', 'at_disconnect', - lambda: self._handle_flag_left_base(team)), - )) + ( + 'call', + 'at_connect', + lambda: self._handle_flag_entered_base(team), + ), + ( + 'call', + 'at_disconnect', + lambda: self._handle_flag_left_base(team), + ), + ), + ) return team @@ -276,9 +305,12 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): if team.enemy_flag_at_base: # And show team name which scored (but actually we could # show here player who returned enemy flag). - self.show_zoom_message(ba.Lstr(resource='nameScoresText', - subs=[('${NAME}', team.name)]), - color=team.color) + self.show_zoom_message( + ba.Lstr( + resource='nameScoresText', subs=[('${NAME}', team.name)] + ), + color=team.color, + ) self._score(team) else: team.enemy_flag_at_base = True @@ -308,15 +340,13 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): 'scale': 0.013, 'color': (1, 1, 0, 1), 'h_align': 'center', - 'position': (bpos[0], bpos[1] + 3.2, bpos[2]) - }) + 'position': (bpos[0], bpos[1] + 3.2, bpos[2]), + }, + ) ba.timer(5.1, tnode.delete) - ba.animate(tnode, 'scale', { - 0.0: 0, - 0.2: 0.013, - 4.8: 0.013, - 5.0: 0 - }) + ba.animate( + tnode, 'scale', {0.0: 0, 0.2: 0.013, 4.8: 0.013, 5.0: 0} + ) def _tick(self) -> None: # If either flag is away from base and not being held, tick down its @@ -344,10 +374,15 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): # to show its auto-return counter. (if there's self-touches # its showing that time). if team.flag_return_touches == 0: - flag.counter.text = (str(flag.time_out_respawn_time) if ( - time_out_counting_down - and flag.time_out_respawn_time is not None - and flag.time_out_respawn_time <= 10) else '') + flag.counter.text = ( + str(flag.time_out_respawn_time) + if ( + time_out_counting_down + and flag.time_out_respawn_time is not None + and flag.time_out_respawn_time <= 10 + ) + else '' + ) flag.counter.color = (1, 1, 1, 0.5) flag.counter.scale = 0.014 @@ -389,8 +424,10 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): if flag.team is team: # Check times here to prevent too much flashing. - if (team.last_flag_leave_time is None - or cur_time - team.last_flag_leave_time > 3.0): + if ( + team.last_flag_leave_time is None + or cur_time - team.last_flag_leave_time > 3.0 + ): ba.playsound(self._alarmsound, position=team.base_pos) self._flash_base(team) team.last_flag_leave_time = cur_time @@ -406,12 +443,15 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): return # No need to return when its at home. if team.touch_return_timer_ticking is None: team.touch_return_timer_ticking = ba.NodeActor( - ba.newnode('sound', - attrs={ - 'sound': self._ticking_sound, - 'positional': False, - 'loop': True - })) + ba.newnode( + 'sound', + attrs={ + 'sound': self._ticking_sound, + 'positional': False, + 'loop': True, + }, + ) + ) flag = team.flag if flag.touch_return_time is not None: flag.touch_return_time -= 0.1 @@ -428,9 +468,9 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): for player in team.players: if player.touching_own_flag > 0: return_score = 10 + 5 * int(self.flag_touch_return_time) - self.stats.player_scored(player, - return_score, - screenmessage=False) + self.stats.player_scored( + player, return_score, screenmessage=False + ) def _handle_touching_own_flag(self, team: Team, connecting: bool) -> None: """Called when a player touches or stops touching their own team flag. @@ -450,14 +490,17 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): player = spaz.getplayer(Player, True) if player: - player.touching_own_flag += (1 if connecting else -1) + player.touching_own_flag += 1 if connecting else -1 # If return-time is zero, just kill it immediately.. otherwise keep # track of touches and count down. if float(self.flag_touch_return_time) <= 0.0: assert team.flag is not None - if (connecting and not team.home_flag_at_base - and team.flag.held_count == 0): + if ( + connecting + and not team.home_flag_at_base + and team.flag.held_count == 0 + ): self._award_players_touching_own_flag(team) ba.getcollision().opposingnode.handlemessage(ba.DieMessage()) @@ -469,7 +512,8 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): team.touch_return_timer = ba.Timer( 0.1, call=ba.Call(self._touch_return_update, team), - repeat=True) + repeat=True, + ) team.touch_return_timer_ticking = None else: team.flag_return_touches -= 1 @@ -480,20 +524,24 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): ba.print_error('CTF flag_return_touches < 0') def _flash_base(self, team: Team, length: float = 2.0) -> None: - light = ba.newnode('light', - attrs={ - 'position': team.base_pos, - 'height_attenuated': False, - 'radius': 0.3, - 'color': team.color - }) + light = ba.newnode( + 'light', + attrs={ + 'position': team.base_pos, + 'height_attenuated': False, + 'radius': 0.3, + 'color': team.color, + }, + ) ba.animate(light, 'intensity', {0.0: 0, 0.25: 2.0, 0.5: 0}, loop=True) ba.timer(length, light.delete) - def spawn_player_spaz(self, - player: Player, - position: Sequence[float] | None = None, - angle: float | None = None) -> PlayerSpaz: + def spawn_player_spaz( + self, + player: Player, + position: Sequence[float] | None = None, + angle: float | None = None, + ) -> PlayerSpaz: """Intercept new spazzes and add our team material for them.""" spaz = super().spawn_player_spaz(player, position, angle) player = spaz.getplayer(Player, True) @@ -510,22 +558,27 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): # (so we can calc restores). assert spaz.node spaz.node.materials = list(spaz.node.materials) + no_physical_mats - spaz.node.roller_materials = list( - spaz.node.roller_materials) + no_physical_mats + spaz.node.roller_materials = ( + list(spaz.node.roller_materials) + no_physical_mats + ) # Pickups and punches shouldn't hit at all though. - spaz.node.punch_materials = list( - spaz.node.punch_materials) + no_collide_mats - spaz.node.pickup_materials = list( - spaz.node.pickup_materials) + no_collide_mats - spaz.node.extras_material = list( - spaz.node.extras_material) + no_collide_mats + spaz.node.punch_materials = ( + list(spaz.node.punch_materials) + no_collide_mats + ) + spaz.node.pickup_materials = ( + list(spaz.node.pickup_materials) + no_collide_mats + ) + spaz.node.extras_material = ( + list(spaz.node.extras_material) + no_collide_mats + ) return spaz def _update_scoreboard(self) -> None: for team in self.teams: - self._scoreboard.set_team_value(team, team.score, - self._score_to_win) + self._scoreboard.set_team_value( + team, team.score, self._score_to_win + ) def handlemessage(self, msg: Any) -> Any: @@ -543,7 +596,8 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): assert isinstance(msg.flag, CTFFlag) try: msg.flag.last_player_to_hold = msg.node.getdelegate( - PlayerSpaz, True).getplayer(Player, True) + PlayerSpaz, True + ).getplayer(Player, True) except ba.NotFoundError: pass diff --git a/dist/ba_data/python/bastd/game/chosenone.py b/dist/ba_data/python/bastd/game/chosenone.py index 6f75748..8604143 100644 --- a/dist/ba_data/python/bastd/game/chosenone.py +++ b/dist/ba_data/python/bastd/game/chosenone.py @@ -42,8 +42,10 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]): """ name = 'Chosen One' - description = ('Be the chosen one for a length of time to win.\n' - 'Kill the chosen one to become it.') + description = ( + 'Be the chosen one for a length of time to win.\n' + 'Kill the chosen one to become it.' + ) available_settings = [ ba.IntSetting( 'Chosen One Time', @@ -99,7 +101,7 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]): 4: ba.getsound('announceFour'), 3: ba.getsound('announceThree'), 2: ba.getsound('announceTwo'), - 1: ba.getsound('announceOne') + 1: ba.getsound('announceOne'), } self._flag_spawn_pos: Sequence[float] | None = None self._reset_region_material: ba.Material | None = None @@ -113,8 +115,9 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]): # Base class overrides self.slow_motion = self._epic_mode - self.default_music = (ba.MusicType.EPIC - if self._epic_mode else ba.MusicType.CHOSEN_ONE) + self.default_music = ( + ba.MusicType.EPIC if self._epic_mode else ba.MusicType.CHOSEN_ONE + ) def get_instance_description(self) -> str | Sequence: return 'There can be only one.' @@ -148,8 +151,7 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]): actions=( ('modify_part_collision', 'collide', True), ('modify_part_collision', 'physical', False), - ('call', 'at_connect', - ba.WeakCall(self._handle_reset_collide)), + ('call', 'at_connect', ba.WeakCall(self._handle_reset_collide)), ), ) @@ -165,8 +167,9 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]): 'position': (pos[0], pos[1] + 0.75, pos[2]), 'scale': (0.5, 0.5, 0.5), 'type': 'sphere', - 'materials': [self._reset_region_material] - }) + 'materials': [self._reset_region_material], + }, + ) def _get_chosen_one_player(self) -> Player | None: # Should never return invalid references; return None in that case. @@ -190,13 +193,15 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]): self._set_chosen_one_player(player) def _flash_flag_spawn(self) -> None: - light = ba.newnode('light', - attrs={ - 'position': self._flag_spawn_pos, - 'color': (1, 1, 1), - 'radius': 0.3, - 'height_attenuated': False - }) + light = ba.newnode( + 'light', + attrs={ + 'position': self._flag_spawn_pos, + 'color': (1, 1, 1), + 'radius': 0.3, + 'height_attenuated': False, + }, + ) ba.animate(light, 'intensity', {0: 0, 0.25: 0.5, 0.5: 0}, loop=True) ba.timer(1.0, light.delete) @@ -213,26 +218,28 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]): else: scoring_team = player.team assert self.stats - self.stats.player_scored(player, - 3, - screenmessage=False, - display=False) + self.stats.player_scored( + player, 3, screenmessage=False, display=False + ) scoring_team.time_remaining = max( - 0, scoring_team.time_remaining - 1) + 0, scoring_team.time_remaining - 1 + ) # Show the count over their head if scoring_team.time_remaining > 0: if isinstance(player.actor, PlayerSpaz) and player.actor: player.actor.set_score_text( - str(scoring_team.time_remaining)) + str(scoring_team.time_remaining) + ) self._update_scoreboard() # announce numbers we have sounds for if scoring_team.time_remaining in self._countdownsounds: ba.playsound( - self._countdownsounds[scoring_team.time_remaining]) + self._countdownsounds[scoring_team.time_remaining] + ) # Winner! if scoring_team.time_remaining <= 0: @@ -250,8 +257,9 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]): def end_game(self) -> None: results = ba.GameResults() for team in self.teams: - results.set_team_score(team, - self._chosen_one_time - team.time_remaining) + results.set_team_score( + team, self._chosen_one_time - team.time_remaining + ) self.end(results=results, announce_delay=0) def _set_chosen_one_player(self, player: Player | None) -> None: @@ -261,23 +269,27 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]): ba.playsound(self._swipsound) if not player: assert self._flag_spawn_pos is not None - self._flag = Flag(color=(1, 0.9, 0.2), - position=self._flag_spawn_pos, - touchable=False) + self._flag = Flag( + color=(1, 0.9, 0.2), + position=self._flag_spawn_pos, + touchable=False, + ) self._chosen_one_player = None # Create a light to highlight the flag; # this will go away when the flag dies. - ba.newnode('light', - owner=self._flag.node, - attrs={ - 'position': self._flag_spawn_pos, - 'intensity': 0.6, - 'height_attenuated': False, - 'volume_intensity_scale': 0.1, - 'radius': 0.1, - 'color': (1.2, 1.2, 0.4) - }) + ba.newnode( + 'light', + owner=self._flag.node, + attrs={ + 'position': self._flag_spawn_pos, + 'intensity': 0.6, + 'height_attenuated': False, + 'volume_intensity_scale': 0.1, + 'radius': 0.1, + 'color': (1.2, 1.2, 0.4), + }, + ) # Also an extra momentary flash. self._flash_flag_spawn() @@ -302,26 +314,29 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]): for c in ba.normalized_color(player.team.color) ] light = player.chosen_light = ba.NodeActor( - ba.newnode('light', - attrs={ - 'intensity': 0.6, - 'height_attenuated': False, - 'volume_intensity_scale': 0.1, - 'radius': 0.13, - 'color': color - })) + ba.newnode( + 'light', + attrs={ + 'intensity': 0.6, + 'height_attenuated': False, + 'volume_intensity_scale': 0.1, + 'radius': 0.13, + 'color': color, + }, + ) + ) assert light.node - ba.animate(light.node, - 'intensity', { - 0: 1.0, - 0.2: 0.4, - 0.4: 1.0 - }, - loop=True) + ba.animate( + light.node, + 'intensity', + {0: 1.0, 0.2: 0.4, 0.4: 1.0}, + loop=True, + ) assert isinstance(player.actor, PlayerSpaz) - player.actor.node.connectattr('position', light.node, - 'position') + player.actor.node.connectattr( + 'position', light.node, 'position' + ) def handlemessage(self, msg: Any) -> Any: if isinstance(msg, ba.PlayerDiedMessage): @@ -330,16 +345,21 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]): player = msg.getplayer(Player) if player is self._get_chosen_one_player(): killerplayer = msg.getkillerplayer(Player) - self._set_chosen_one_player(None if ( - killerplayer is None or killerplayer is player - or not killerplayer.is_alive()) else killerplayer) + self._set_chosen_one_player( + None + if ( + killerplayer is None + or killerplayer is player + or not killerplayer.is_alive() + ) + else killerplayer + ) self.respawn_player(player) else: super().handlemessage(msg) def _update_scoreboard(self) -> None: for team in self.teams: - self._scoreboard.set_team_value(team, - team.time_remaining, - self._chosen_one_time, - countdown=True) + self._scoreboard.set_team_value( + team, team.time_remaining, self._chosen_one_time, countdown=True + ) diff --git a/dist/ba_data/python/bastd/game/conquest.py b/dist/ba_data/python/bastd/game/conquest.py index 88650c0..13f6f29 100644 --- a/dist/ba_data/python/bastd/game/conquest.py +++ b/dist/ba_data/python/bastd/game/conquest.py @@ -125,8 +125,9 @@ class ConquestGame(ba.TeamGameActivity[Player, Team]): # Base class overrides. self.slow_motion = self._epic_mode - self.default_music = (ba.MusicType.EPIC - if self._epic_mode else ba.MusicType.GRAND_ROMP) + self.default_music = ( + ba.MusicType.EPIC if self._epic_mode else ba.MusicType.GRAND_ROMP + ) # We want flags to tell us they've been hit but not react physically. self._extraflagmat.add_actions( @@ -134,7 +135,8 @@ class ConquestGame(ba.TeamGameActivity[Player, Team]): actions=( ('modify_part_collision', 'collide', True), ('call', 'at_connect', self._handle_flag_player_collide), - )) + ), + ) def get_instance_description(self) -> str | Sequence: return 'Secure all ${ARG1} flags.', len(self.map.flag_points) @@ -161,20 +163,22 @@ class ConquestGame(ba.TeamGameActivity[Player, Team]): # Set up flags with marker lights. for i, flag_point in enumerate(self.map.flag_points): point = flag_point - flag = ConquestFlag(position=point, - touchable=False, - materials=[self._extraflagmat]) + flag = ConquestFlag( + position=point, touchable=False, materials=[self._extraflagmat] + ) self._flags.append(flag) Flag.project_stand(point) - flag.light = ba.newnode('light', - owner=flag.node, - attrs={ - 'position': point, - 'intensity': 0.25, - 'height_attenuated': False, - 'radius': 0.3, - 'color': (1, 1, 1) - }) + flag.light = ba.newnode( + 'light', + owner=flag.node, + attrs={ + 'position': point, + 'intensity': 0.25, + 'height_attenuated': False, + 'radius': 0.3, + 'color': (1, 1, 1), + }, + ) # Give teams a flag to start with. for i, team in enumerate(self.teams): @@ -209,8 +213,9 @@ class ConquestGame(ba.TeamGameActivity[Player, Team]): player.respawn_icon = None if team.flags_held == len(self._flags): self.end_game() - self._scoreboard.set_team_value(team, team.flags_held, - len(self._flags)) + self._scoreboard.set_team_value( + team, team.flags_held, len(self._flags) + ) def end_game(self) -> None: results = ba.GameResults() @@ -221,12 +226,14 @@ class ConquestGame(ba.TeamGameActivity[Player, Team]): def _flash_flag(self, flag: ConquestFlag, length: float = 1.0) -> None: assert flag.node assert flag.light - light = ba.newnode('light', - attrs={ - 'position': flag.node.position, - 'height_attenuated': False, - 'color': flag.light.color - }) + light = ba.newnode( + 'light', + attrs={ + 'position': flag.node.position, + 'height_attenuated': False, + 'color': flag.light.color, + }, + ) ba.animate(light, 'intensity', {0: 0, 0.25: 1, 0.5: 0}, loop=True) ba.timer(length, light.delete) @@ -234,9 +241,9 @@ class ConquestGame(ba.TeamGameActivity[Player, Team]): collision = ba.getcollision() try: flag = collision.sourcenode.getdelegate(ConquestFlag, True) - player = collision.opposingnode.getdelegate(PlayerSpaz, - True).getplayer( - Player, True) + player = collision.opposingnode.getdelegate( + PlayerSpaz, True + ).getplayer(Player, True) except ba.NotFoundError: return assert flag.light @@ -253,10 +260,12 @@ class ConquestGame(ba.TeamGameActivity[Player, Team]): # Respawn any players on this team that were in limbo due to the # lack of a flag for their team. for otherplayer in self.players: - if (otherplayer.team is flag.team - and otherplayer.actor is not None - and not otherplayer.is_alive() - and otherplayer.respawn_timer is None): + if ( + otherplayer.team is flag.team + and otherplayer.actor is not None + and not otherplayer.is_alive() + and otherplayer.respawn_timer is None + ): self.spawn_player(otherplayer) def handlemessage(self, msg: Any) -> Any: @@ -276,8 +285,9 @@ class ConquestGame(ba.TeamGameActivity[Player, Team]): def spawn_player(self, player: Player) -> ba.Actor: # We spawn players at different places based on what flags are held. - return self.spawn_player_spaz(player, - self._get_player_spawn_position(player)) + return self.spawn_player_spaz( + player, self._get_player_spawn_position(player) + ) def _get_player_spawn_position(self, player: Player) -> Sequence[float]: @@ -298,8 +308,9 @@ class ConquestGame(ba.TeamGameActivity[Player, Team]): spt = self.map.spawn_by_flag_points[spawn] our_pt = ba.Vec3(spt[0], spt[1], spt[2]) for otherspawn in [ - i for i in range(spawn_count) - if self._flags[i].team is not player.team + i + for i in range(spawn_count) + if self._flags[i].team is not player.team ]: spt = self.map.spawn_by_flag_points[otherspawn] their_pt = ba.Vec3(spt[0], spt[1], spt[2]) @@ -311,6 +322,9 @@ class ConquestGame(ba.TeamGameActivity[Player, Team]): pos = self.map.spawn_by_flag_points[closest_spawn] x_range = (-0.5, 0.5) if pos[3] == 0.0 else (-pos[3], pos[3]) z_range = (-0.5, 0.5) if pos[5] == 0.0 else (-pos[5], pos[5]) - pos = (pos[0] + random.uniform(*x_range), pos[1], - pos[2] + random.uniform(*z_range)) + pos = ( + pos[0] + random.uniform(*x_range), + pos[1], + pos[2] + random.uniform(*z_range), + ) return pos diff --git a/dist/ba_data/python/bastd/game/deathmatch.py b/dist/ba_data/python/bastd/game/deathmatch.py index b1ef3dc..b82df09 100644 --- a/dist/ba_data/python/bastd/game/deathmatch.py +++ b/dist/ba_data/python/bastd/game/deathmatch.py @@ -40,7 +40,8 @@ class DeathMatchGame(ba.TeamGameActivity[Player, Team]): @classmethod def get_available_settings( - cls, sessiontype: type[ba.Session]) -> list[ba.Setting]: + cls, sessiontype: type[ba.Session] + ) -> list[ba.Setting]: settings = [ ba.IntSetting( 'Kills to Win Per Player', @@ -81,14 +82,16 @@ class DeathMatchGame(ba.TeamGameActivity[Player, Team]): # suiciding until you get a good drop) if issubclass(sessiontype, ba.FreeForAllSession): settings.append( - ba.BoolSetting('Allow Negative Scores', default=False)) + ba.BoolSetting('Allow Negative Scores', default=False) + ) return settings @classmethod def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool: - return (issubclass(sessiontype, ba.DualTeamSession) - or issubclass(sessiontype, ba.FreeForAllSession)) + return issubclass(sessiontype, ba.DualTeamSession) or issubclass( + sessiontype, ba.FreeForAllSession + ) @classmethod def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]: @@ -100,16 +103,17 @@ class DeathMatchGame(ba.TeamGameActivity[Player, Team]): self._score_to_win: int | None = None self._dingsound = ba.getsound('dingSmall') self._epic_mode = bool(settings['Epic Mode']) - self._kills_to_win_per_player = int( - settings['Kills to Win Per Player']) + self._kills_to_win_per_player = int(settings['Kills to Win Per Player']) self._time_limit = float(settings['Time Limit']) self._allow_negative_scores = bool( - settings.get('Allow Negative Scores', False)) + settings.get('Allow Negative Scores', False) + ) # Base class overrides. self.slow_motion = self._epic_mode - self.default_music = (ba.MusicType.EPIC if self._epic_mode else - ba.MusicType.TO_THE_DEATH) + self.default_music = ( + ba.MusicType.EPIC if self._epic_mode else ba.MusicType.TO_THE_DEATH + ) def get_instance_description(self) -> str | Sequence: return 'Crush ${ARG1} of your enemies.', self._score_to_win @@ -127,8 +131,9 @@ class DeathMatchGame(ba.TeamGameActivity[Player, Team]): self.setup_standard_powerup_drops() # Base kills needed to win on the size of the largest team. - self._score_to_win = (self._kills_to_win_per_player * - max(1, max(len(t.players) for t in self.teams))) + self._score_to_win = self._kills_to_win_per_player * max( + 1, max(len(t.players) for t in self.teams) + ) self._update_scoreboard() def handlemessage(self, msg: Any) -> Any: @@ -169,10 +174,11 @@ class DeathMatchGame(ba.TeamGameActivity[Player, Team]): # In FFA show scores since its hard to find on the scoreboard. if isinstance(killer.actor, PlayerSpaz) and killer.actor: - killer.actor.set_score_text(str(killer.team.score) + '/' + - str(self._score_to_win), - color=killer.team.color, - flash=True) + killer.actor.set_score_text( + str(killer.team.score) + '/' + str(self._score_to_win), + color=killer.team.color, + flash=True, + ) self._update_scoreboard() @@ -189,8 +195,9 @@ class DeathMatchGame(ba.TeamGameActivity[Player, Team]): def _update_scoreboard(self) -> None: for team in self.teams: - self._scoreboard.set_team_value(team, team.score, - self._score_to_win) + self._scoreboard.set_team_value( + team, team.score, self._score_to_win + ) def end_game(self) -> None: results = ba.GameResults() diff --git a/dist/ba_data/python/bastd/game/easteregghunt.py b/dist/ba_data/python/bastd/game/easteregghunt.py index 1dc4de0..2bc0454 100644 --- a/dist/ba_data/python/bastd/game/easteregghunt.py +++ b/dist/ba_data/python/bastd/game/easteregghunt.py @@ -58,9 +58,11 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]): # We support teams, free-for-all, and co-op sessions. @classmethod def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool: - return (issubclass(sessiontype, ba.CoopSession) - or issubclass(sessiontype, ba.DualTeamSession) - or issubclass(sessiontype, ba.FreeForAllSession)) + return ( + issubclass(sessiontype, ba.CoopSession) + or issubclass(sessiontype, ba.DualTeamSession) + or issubclass(sessiontype, ba.FreeForAllSession) + ) def __init__(self, settings: dict): super().__init__(settings) @@ -78,7 +80,8 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]): self.egg_material = ba.Material() self.egg_material.add_actions( conditions=('they_have_material', shared.player_material), - actions=(('call', 'at_connect', self._on_egg_player_collide), )) + actions=(('call', 'at_connect', self._on_egg_player_collide),), + ) self._eggs: list[Egg] = [] self._update_timer: ba.Timer | None = None self._countdown: OnScreenCountdown | None = None @@ -86,8 +89,9 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]): # Base class overrides self.slow_motion = self._epic_mode - self.default_music = (ba.MusicType.EPIC if self._epic_mode else - ba.MusicType.FORWARD_MARCH) + self.default_music = ( + ba.MusicType.EPIC if self._epic_mode else ba.MusicType.FORWARD_MARCH + ) def on_team_join(self, team: Team) -> None: if self.has_begun(): @@ -132,9 +136,9 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]): # who just left/etc. try: egg = collision.sourcenode.getdelegate(Egg, True) - player = collision.opposingnode.getdelegate(PlayerSpaz, - True).getplayer( - Player, True) + player = collision.opposingnode.getdelegate( + PlayerSpaz, True + ).getplayer(Player, True) except ba.NotFoundError: return @@ -153,13 +157,15 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]): ba.playsound(self._collect_sound, 0.5, position=egg.node.position) # Create a flash. - light = ba.newnode('light', - attrs={ - 'position': egg.node.position, - 'height_attenuated': False, - 'radius': 0.1, - 'color': (1, 1, 0) - }) + light = ba.newnode( + 'light', + attrs={ + 'position': egg.node.position, + 'height_attenuated': False, + 'radius': 0.1, + 'color': (1, 1, 0), + }, + ) ba.animate(light, 'intensity', {0: 0, 0.1: 1.0, 0.2: 0}, loop=False) ba.timer(0.200, light.delete) egg.handlemessage(ba.DieMessage()) @@ -178,8 +184,9 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]): # Occasionally spawn a land-mine in addition. if self._pro_mode and random.random() < 0.25: - mine = Bomb(position=(xpos, ypos, zpos), - bomb_type='land_mine').autoretain() + mine = Bomb( + position=(xpos, ypos, zpos), bomb_type='land_mine' + ).autoretain() mine.arm() else: self._eggs.append(Egg(position=(xpos, ypos, zpos))) @@ -197,7 +204,8 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]): assert self.initialplayerinfos is not None respawn_time = 2.0 + len(self.initialplayerinfos) * 1.0 player.respawn_timer = ba.Timer( - respawn_time, ba.Call(self.spawn_player_if_exists, player)) + respawn_time, ba.Call(self.spawn_player_if_exists, player) + ) player.respawn_icon = RespawnIcon(player, respawn_time) # Whenever our evil bunny dies, respawn him and spew some eggs. @@ -208,9 +216,14 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]): for _i in range(6): spread = 0.4 self._eggs.append( - Egg(position=(pos[0] + random.uniform(-spread, spread), - pos[1] + random.uniform(-spread, spread), - pos[2] + random.uniform(-spread, spread)))) + Egg( + position=( + pos[0] + random.uniform(-spread, spread), + pos[1] + random.uniform(-spread, spread), + pos[2] + random.uniform(-spread, spread), + ) + ) + ) else: # Default handler. return super().handlemessage(msg) @@ -238,24 +251,27 @@ class Egg(ba.Actor): # Spawn just above the provided point. self._spawn_pos = (position[0], position[1] + 1.0, position[2]) - ctex = (activity.egg_tex_1, activity.egg_tex_2, - activity.egg_tex_3)[random.randrange(3)] + ctex = (activity.egg_tex_1, activity.egg_tex_2, activity.egg_tex_3)[ + random.randrange(3) + ] mats = [shared.object_material, activity.egg_material] - self.node = ba.newnode('prop', - delegate=self, - attrs={ - 'model': activity.egg_model, - 'color_texture': ctex, - 'body': 'capsule', - 'reflection': 'soft', - 'model_scale': 0.5, - 'body_scale': 0.6, - 'density': 4.0, - 'reflection_scale': [0.15], - 'shadow_size': 0.6, - 'position': self._spawn_pos, - 'materials': mats - }) + self.node = ba.newnode( + 'prop', + delegate=self, + attrs={ + 'model': activity.egg_model, + 'color_texture': ctex, + 'body': 'capsule', + 'reflection': 'soft', + 'model_scale': 0.5, + 'body_scale': 0.6, + 'density': 4.0, + 'reflection_scale': [0.15], + 'shadow_size': 0.6, + 'position': self._spawn_pos, + 'materials': mats, + }, + ) def exists(self) -> bool: return bool(self.node) @@ -268,10 +284,20 @@ class Egg(ba.Actor): if self.node: assert msg.force_direction is not None self.node.handlemessage( - 'impulse', msg.pos[0], msg.pos[1], msg.pos[2], - msg.velocity[0], msg.velocity[1], msg.velocity[2], - 1.0 * msg.magnitude, 1.0 * msg.velocity_magnitude, - msg.radius, 0, msg.force_direction[0], - msg.force_direction[1], msg.force_direction[2]) + 'impulse', + msg.pos[0], + msg.pos[1], + msg.pos[2], + msg.velocity[0], + msg.velocity[1], + msg.velocity[2], + 1.0 * msg.magnitude, + 1.0 * msg.velocity_magnitude, + msg.radius, + 0, + msg.force_direction[0], + msg.force_direction[1], + msg.force_direction[2], + ) else: super().handlemessage(msg) diff --git a/dist/ba_data/python/bastd/game/elimination.py b/dist/ba_data/python/bastd/game/elimination.py index e3d91e4..db31fcb 100644 --- a/dist/ba_data/python/bastd/game/elimination.py +++ b/dist/ba_data/python/bastd/game/elimination.py @@ -20,16 +20,18 @@ if TYPE_CHECKING: class Icon(ba.Actor): """Creates in in-game icon on screen.""" - def __init__(self, - player: Player, - position: tuple[float, float], - scale: float, - show_lives: bool = True, - show_death: bool = True, - name_scale: float = 1.0, - name_maxwidth: float = 115.0, - flatness: float = 1.0, - shadow: float = 1.0): + def __init__( + self, + player: Player, + position: tuple[float, float], + scale: float, + show_lives: bool = True, + show_death: bool = True, + name_scale: float = 1.0, + name_maxwidth: float = 115.0, + flatness: float = 1.0, + shadow: float = 1.0, + ): super().__init__() self._player = player @@ -39,19 +41,21 @@ class Icon(ba.Actor): self._outline_tex = ba.gettexture('characterIconMask') icon = player.get_icon() - self.node = ba.newnode('image', - delegate=self, - attrs={ - 'texture': icon['texture'], - 'tint_texture': icon['tint_texture'], - 'tint_color': icon['tint_color'], - 'vr_depth': 400, - 'tint2_color': icon['tint2_color'], - 'mask_texture': self._outline_tex, - 'opacity': 1.0, - 'absolute_scale': True, - 'attach': 'bottomCenter' - }) + self.node = ba.newnode( + 'image', + delegate=self, + attrs={ + 'texture': icon['texture'], + 'tint_texture': icon['tint_texture'], + 'tint_color': icon['tint_color'], + 'vr_depth': 400, + 'tint2_color': icon['tint2_color'], + 'mask_texture': self._outline_tex, + 'opacity': 1.0, + 'absolute_scale': True, + 'attach': 'bottomCenter', + }, + ) self._name_text = ba.newnode( 'text', owner=self.node, @@ -65,25 +69,29 @@ class Icon(ba.Actor): 'shadow': shadow, 'flatness': flatness, 'h_attach': 'center', - 'v_attach': 'bottom' - }) + 'v_attach': 'bottom', + }, + ) if self._show_lives: - self._lives_text = ba.newnode('text', - owner=self.node, - attrs={ - 'text': 'x0', - 'color': (1, 1, 0.5), - 'h_align': 'left', - 'vr_depth': 430, - 'shadow': 1.0, - 'flatness': 1.0, - 'h_attach': 'center', - 'v_attach': 'bottom' - }) + self._lives_text = ba.newnode( + 'text', + owner=self.node, + attrs={ + 'text': 'x0', + 'color': (1, 1, 0.5), + 'h_align': 'left', + 'vr_depth': 430, + 'shadow': 1.0, + 'flatness': 1.0, + 'h_attach': 'center', + 'v_attach': 'bottom', + }, + ) self.set_position_and_scale(position, scale) - def set_position_and_scale(self, position: tuple[float, float], - scale: float) -> None: + def set_position_and_scale( + self, position: tuple[float, float], scale: float + ) -> None: """(Re)position the icon.""" assert self.node self.node.position = position @@ -91,8 +99,10 @@ class Icon(ba.Actor): self._name_text.position = (position[0], position[1] + scale * 52.0) self._name_text.scale = 1.0 * scale * self._name_scale if self._show_lives: - self._lives_text.position = (position[0] + scale * 10.0, - position[1] - scale * 43.0) + self._lives_text.position = ( + position[0] + scale * 10.0, + position[1] - scale * 43.0, + ) self._lives_text.scale = 1.0 * scale def update_for_lives(self) -> None: @@ -125,7 +135,9 @@ class Icon(ba.Actor): return if self._show_death: ba.animate( - self.node, 'opacity', { + self.node, + 'opacity', + { 0.00: 1.0, 0.05: 0.0, 0.10: 1.0, @@ -137,8 +149,9 @@ class Icon(ba.Actor): 0.40: 1.0, 0.45: 0.0, 0.50: 1.0, - 0.55: 0.2 - }) + 0.55: 0.2, + }, + ) lives = self._player.lives if lives == 0: ba.timer(0.6, self.update_for_lives) @@ -172,9 +185,9 @@ class EliminationGame(ba.TeamGameActivity[Player, Team]): name = 'Elimination' description = 'Last remaining alive wins.' - scoreconfig = ba.ScoreConfig(label='Survived', - scoretype=ba.ScoreType.SECONDS, - none_is_winner=True) + scoreconfig = ba.ScoreConfig( + label='Survived', scoretype=ba.ScoreType.SECONDS, none_is_winner=True + ) # Show messages when players die since it's meaningful here. announce_player_deaths = True @@ -182,7 +195,8 @@ class EliminationGame(ba.TeamGameActivity[Player, Team]): @classmethod def get_available_settings( - cls, sessiontype: type[ba.Session]) -> list[ba.Setting]: + cls, sessiontype: type[ba.Session] + ) -> list[ba.Setting]: settings = [ ba.IntSetting( 'Lives Per Player', @@ -219,13 +233,15 @@ class EliminationGame(ba.TeamGameActivity[Player, Team]): if issubclass(sessiontype, ba.DualTeamSession): settings.append(ba.BoolSetting('Solo Mode', default=False)) settings.append( - ba.BoolSetting('Balance Total Lives', default=False)) + ba.BoolSetting('Balance Total Lives', default=False) + ) return settings @classmethod def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool: - return (issubclass(sessiontype, ba.DualTeamSession) - or issubclass(sessiontype, ba.FreeForAllSession)) + return issubclass(sessiontype, ba.DualTeamSession) or issubclass( + sessiontype, ba.FreeForAllSession + ) @classmethod def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]: @@ -241,21 +257,29 @@ class EliminationGame(ba.TeamGameActivity[Player, Team]): self._lives_per_player = int(settings['Lives Per Player']) self._time_limit = float(settings['Time Limit']) self._balance_total_lives = bool( - settings.get('Balance Total Lives', False)) + settings.get('Balance Total Lives', False) + ) self._solo_mode = bool(settings.get('Solo Mode', False)) # Base class overrides: self.slow_motion = self._epic_mode - self.default_music = (ba.MusicType.EPIC - if self._epic_mode else ba.MusicType.SURVIVAL) + self.default_music = ( + ba.MusicType.EPIC if self._epic_mode else ba.MusicType.SURVIVAL + ) def get_instance_description(self) -> str | Sequence: - return 'Last team standing wins.' if isinstance( - self.session, ba.DualTeamSession) else 'Last one standing wins.' + return ( + 'Last team standing wins.' + if isinstance(self.session, ba.DualTeamSession) + else 'Last one standing wins.' + ) def get_instance_description_short(self) -> str | Sequence: - return 'last team standing wins' if isinstance( - self.session, ba.DualTeamSession) else 'last one standing wins' + return ( + 'last team standing wins' + if isinstance(self.session, ba.DualTeamSession) + else 'last one standing wins' + ) def on_player_join(self, player: Player) -> None: player.lives = self._lives_per_player @@ -280,35 +304,43 @@ class EliminationGame(ba.TeamGameActivity[Player, Team]): self.setup_standard_powerup_drops() if self._solo_mode: self._vs_text = ba.NodeActor( - ba.newnode('text', - attrs={ - 'position': (0, 105), - 'h_attach': 'center', - 'h_align': 'center', - 'maxwidth': 200, - 'shadow': 0.5, - 'vr_depth': 390, - 'scale': 0.6, - 'v_attach': 'bottom', - 'color': (0.8, 0.8, 0.3, 1.0), - 'text': ba.Lstr(resource='vsText') - })) + ba.newnode( + 'text', + attrs={ + 'position': (0, 105), + 'h_attach': 'center', + 'h_align': 'center', + 'maxwidth': 200, + 'shadow': 0.5, + 'vr_depth': 390, + 'scale': 0.6, + 'v_attach': 'bottom', + 'color': (0.8, 0.8, 0.3, 1.0), + 'text': ba.Lstr(resource='vsText'), + }, + ) + ) # If balance-team-lives is on, add lives to the smaller team until # total lives match. - if (isinstance(self.session, ba.DualTeamSession) - and self._balance_total_lives and self.teams[0].players - and self.teams[1].players): + if ( + isinstance(self.session, ba.DualTeamSession) + and self._balance_total_lives + and self.teams[0].players + and self.teams[1].players + ): if self._get_total_team_lives( - self.teams[0]) < self._get_total_team_lives(self.teams[1]): + self.teams[0] + ) < self._get_total_team_lives(self.teams[1]): lesser_team = self.teams[0] greater_team = self.teams[1] else: lesser_team = self.teams[1] greater_team = self.teams[0] add_index = 0 - while (self._get_total_team_lives(lesser_team) < - self._get_total_team_lives(greater_team)): + while self._get_total_team_lives( + lesser_team + ) < self._get_total_team_lives(greater_team): lesser_team.players[add_index].lives += 1 add_index = (add_index + 1) % len(lesser_team.players) @@ -367,22 +399,26 @@ class EliminationGame(ba.TeamGameActivity[Player, Team]): test_lives = 1 while True: players_with_lives = [ - p for p in team.spawn_order + p + for p in team.spawn_order if p and p.lives >= test_lives ] if not players_with_lives: break for player in players_with_lives: player.icons.append( - Icon(player, - position=(xval, (40 if is_first else 25)), - scale=1.0 if is_first else 0.5, - name_maxwidth=130 if is_first else 75, - name_scale=0.8 if is_first else 1.0, - flatness=0.0 if is_first else 1.0, - shadow=0.5 if is_first else 1.0, - show_death=is_first, - show_lives=False)) + Icon( + player, + position=(xval, (40 if is_first else 25)), + scale=1.0 if is_first else 0.5, + name_maxwidth=130 if is_first else 75, + name_scale=0.8 if is_first else 1.0, + flatness=0.0 if is_first else 1.0, + shadow=0.5 if is_first else 1.0, + show_death=is_first, + show_lives=False, + ) + ) xval += x_offs * (0.8 if is_first else 0.56) is_first = False test_lives += 1 @@ -424,7 +460,8 @@ class EliminationGame(ba.TeamGameActivity[Player, Team]): for team in self.teams: start_pos = ba.Vec3(self.map.get_start_position(team.id)) points.append( - ((start_pos - player_pos).length(), start_pos)) + ((start_pos - player_pos).length(), start_pos) + ) # Hmm.. we need to sorting vectors too? points.sort(key=lambda x: x[0]) return points[-1][1] @@ -447,12 +484,14 @@ class EliminationGame(ba.TeamGameActivity[Player, Team]): if not player or not player.is_alive() or not player.node: return - popuptext.PopupText('x' + str(player.lives - 1), - color=(1, 1, 0, 1), - offset=(0, -0.8, 0), - random_offset=0.0, - scale=1.8, - position=player.node.position).autoretain() + popuptext.PopupText( + 'x' + str(player.lives - 1), + color=(1, 1, 0, 1), + offset=(0, -0.8, 0), + random_offset=0.0, + scale=1.8, + position=player.node.position, + ).autoretain() def on_player_leave(self, player: Player) -> None: super().on_player_leave(player) @@ -487,8 +526,9 @@ class EliminationGame(ba.TeamGameActivity[Player, Team]): player.lives -= 1 if player.lives < 0: ba.print_error( - "Got lives < 0 in Elim; this shouldn't happen. solo:" + - str(self._solo_mode)) + "Got lives < 0 in Elim; this shouldn't happen. solo:" + + str(self._solo_mode) + ) player.lives = 0 # If we have any icons, update their state. @@ -505,8 +545,9 @@ class EliminationGame(ba.TeamGameActivity[Player, Team]): # If the whole team is now dead, mark their survival time. if self._get_total_team_lives(player.team) == 0: assert self._start_time is not None - player.team.survival_seconds = int(ba.time() - - self._start_time) + player.team.survival_seconds = int( + ba.time() - self._start_time + ) else: # Otherwise, in regular mode, respawn. if not self._solo_mode: @@ -540,9 +581,10 @@ class EliminationGame(ba.TeamGameActivity[Player, Team]): def _get_living_teams(self) -> list[Team]: return [ - team for team in self.teams - if len(team.players) > 0 and any(player.lives > 0 - for player in team.players) + team + for team in self.teams + if len(team.players) > 0 + and any(player.lives > 0 for player in team.players) ] def end_game(self) -> None: diff --git a/dist/ba_data/python/bastd/game/football.py b/dist/ba_data/python/bastd/game/football.py index 2183fa9..a88583e 100644 --- a/dist/ba_data/python/bastd/game/football.py +++ b/dist/ba_data/python/bastd/game/football.py @@ -17,13 +17,28 @@ from bastd.actor.playerspaz import PlayerSpaz from bastd.actor.scoreboard import Scoreboard from bastd.actor.respawnicon import RespawnIcon from bastd.actor.powerupbox import PowerupBoxFactory, PowerupBox -from bastd.actor.flag import (FlagFactory, Flag, FlagPickedUpMessage, - FlagDroppedMessage, FlagDiedMessage) -from bastd.actor.spazbot import (SpazBotDiedMessage, SpazBotPunchedMessage, - SpazBotSet, BrawlerBotLite, BrawlerBot, - BomberBotLite, BomberBot, TriggerBot, - ChargerBot, TriggerBotPro, BrawlerBotPro, - StickyBot, ExplodeyBot) +from bastd.actor.flag import ( + FlagFactory, + Flag, + FlagPickedUpMessage, + FlagDroppedMessage, + FlagDiedMessage, +) +from bastd.actor.spazbot import ( + SpazBotDiedMessage, + SpazBotPunchedMessage, + SpazBotSet, + BrawlerBotLite, + BrawlerBot, + BomberBotLite, + BomberBot, + TriggerBot, + ChargerBot, + TriggerBotPro, + BrawlerBotPro, + StickyBot, + ExplodeyBot, +) if TYPE_CHECKING: from typing import Any, Sequence @@ -35,23 +50,25 @@ class FootballFlag(Flag): """Custom flag class for football games.""" def __init__(self, position: Sequence[float]): - super().__init__(position=position, - dropped_timeout=20, - color=(1.0, 1.0, 0.3)) + super().__init__( + position=position, dropped_timeout=20, color=(1.0, 1.0, 0.3) + ) assert self.node self.last_holding_player: ba.Player | None = None self.node.is_area_of_interest = True self.respawn_timer: ba.Timer | None = None self.scored = False self.held_count = 0 - self.light = ba.newnode('light', - owner=self.node, - attrs={ - 'intensity': 0.25, - 'height_attenuated': False, - 'radius': 0.2, - 'color': (0.9, 0.7, 0.0) - }) + self.light = ba.newnode( + 'light', + owner=self.node, + attrs={ + 'intensity': 0.25, + 'height_attenuated': False, + 'radius': 0.2, + 'color': (0.9, 0.7, 0.0), + }, + ) self.node.connectattr('position', self.light, 'position') @@ -135,7 +152,8 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]): ('modify_part_collision', 'collide', True), ('modify_part_collision', 'physical', False), ('call', 'at_connect', self._handle_score), - )) + ), + ) self._flag_spawn_pos: Sequence[float] | None = None self._score_regions: list[ba.NodeActor] = [] self._flag: FootballFlag | None = None @@ -145,8 +163,9 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]): self._time_limit = float(settings['Time Limit']) self._epic_mode = bool(settings['Epic Mode']) self.slow_motion = self._epic_mode - self.default_music = (ba.MusicType.EPIC - if self._epic_mode else ba.MusicType.FOOTBALL) + self.default_music = ( + ba.MusicType.EPIC if self._epic_mode else ba.MusicType.FOOTBALL + ) def get_instance_description(self) -> str | Sequence: touchdowns = self._score_to_win / 7 @@ -170,27 +189,35 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]): super().on_begin() self.setup_standard_time_limit(self._time_limit) self.setup_standard_powerup_drops() - self._flag_spawn_pos = (self.map.get_flag_position(None)) + self._flag_spawn_pos = self.map.get_flag_position(None) self._spawn_flag() defs = self.map.defs self._score_regions.append( ba.NodeActor( - ba.newnode('region', - attrs={ - 'position': defs.boxes['goal1'][0:3], - 'scale': defs.boxes['goal1'][6:9], - 'type': 'box', - 'materials': (self._score_region_material, ) - }))) + ba.newnode( + 'region', + attrs={ + 'position': defs.boxes['goal1'][0:3], + 'scale': defs.boxes['goal1'][6:9], + 'type': 'box', + 'materials': (self._score_region_material,), + }, + ) + ) + ) self._score_regions.append( ba.NodeActor( - ba.newnode('region', - attrs={ - 'position': defs.boxes['goal2'][0:3], - 'scale': defs.boxes['goal2'][6:9], - 'type': 'box', - 'materials': (self._score_region_material, ) - }))) + ba.newnode( + 'region', + attrs={ + 'position': defs.boxes['goal2'][0:3], + 'scale': defs.boxes['goal2'][6:9], + 'type': 'box', + 'materials': (self._score_region_material,), + }, + ) + ) + ) self._update_scoreboard() ba.playsound(self._chant_sound) @@ -225,11 +252,13 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]): # If someone on this team was last to touch it, # give them points. assert self._flag is not None - if (self._flag.last_holding_player - and team == self._flag.last_holding_player.team): - self.stats.player_scored(self._flag.last_holding_player, - 50, - big_message=True) + if ( + self._flag.last_holding_player + and team == self._flag.last_holding_player.team + ): + self.stats.player_scored( + self._flag.last_holding_player, 50, big_message=True + ) # End the game if we won. if team.score >= self._score_to_win: self.end_game() @@ -240,12 +269,14 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]): # Kill the flag (it'll respawn shortly). ba.timer(1.0, self._kill_flag) - light = ba.newnode('light', - attrs={ - 'position': ba.getcollision().position, - 'height_attenuated': False, - 'color': (1, 0, 0) - }) + light = ba.newnode( + 'light', + attrs={ + 'position': ba.getcollision().position, + 'height_attenuated': False, + 'color': (1, 0, 0), + }, + ) ba.animate(light, 'intensity', {0.0: 0, 0.5: 1, 1.0: 0}, loop=True) ba.timer(1.0, light.delete) ba.cameraflash(duration=10.0) @@ -260,15 +291,17 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]): def _update_scoreboard(self) -> None: assert self._scoreboard is not None for team in self.teams: - self._scoreboard.set_team_value(team, team.score, - self._score_to_win) + self._scoreboard.set_team_value( + team, team.score, self._score_to_win + ) def handlemessage(self, msg: Any) -> Any: if isinstance(msg, FlagPickedUpMessage): assert isinstance(msg.flag, FootballFlag) try: msg.flag.last_holding_player = msg.node.getdelegate( - PlayerSpaz, True).getplayer(Player, True) + PlayerSpaz, True + ).getplayer(Player, True) except ba.NotFoundError: pass msg.flag.held_count += 1 @@ -288,21 +321,23 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]): if not self.has_ended(): self._flag_respawn_timer = ba.Timer(3.0, self._spawn_flag) self._flag_respawn_light = ba.NodeActor( - ba.newnode('light', - attrs={ - 'position': self._flag_spawn_pos, - 'height_attenuated': False, - 'radius': 0.15, - 'color': (1.0, 1.0, 0.3) - })) + ba.newnode( + 'light', + attrs={ + 'position': self._flag_spawn_pos, + 'height_attenuated': False, + 'radius': 0.15, + 'color': (1.0, 1.0, 0.3), + }, + ) + ) assert self._flag_respawn_light.node - ba.animate(self._flag_respawn_light.node, - 'intensity', { - 0.0: 0, - 0.25: 0.15, - 0.5: 0 - }, - loop=True) + ba.animate( + self._flag_respawn_light.node, + 'intensity', + {0.0: 0, 0.25: 0.15, 0.5: 0}, + loop=True, + ) ba.timer(3.0, self._flag_respawn_light.node.delete) else: @@ -310,12 +345,14 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]): super().handlemessage(msg) def _flash_flag_spawn(self) -> None: - light = ba.newnode('light', - attrs={ - 'position': self._flag_spawn_pos, - 'height_attenuated': False, - 'color': (1, 1, 0) - }) + light = ba.newnode( + 'light', + attrs={ + 'position': self._flag_spawn_pos, + 'height_attenuated': False, + 'color': (1, 1, 0), + }, + ) ba.animate(light, 'intensity', {0: 0, 0.25: 0.25, 0.5: 0}, loop=True) ba.timer(1.0, light.delete) @@ -332,8 +369,9 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): name = 'Football' tips = ['Use the pick-up button to grab the flag < ${PICKUP} >'] - scoreconfig = ba.ScoreConfig(scoretype=ba.ScoreType.MILLISECONDS, - version='B') + scoreconfig = ba.ScoreConfig( + scoretype=ba.ScoreType.MILLISECONDS, version='B' + ) default_music = ba.MusicType.FOOTBALL @@ -375,7 +413,8 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): ('modify_part_collision', 'collide', True), ('modify_part_collision', 'physical', False), ('call', 'at_connect', self._handle_score), - )) + ), + ) self._powerup_center = (0, 2, 0) self._powerup_spread = (10, 5.5) self._player_has_dropped_bomb = False @@ -412,34 +451,44 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): defs = self.map.defs self._score_regions.append( ba.NodeActor( - ba.newnode('region', - attrs={ - 'position': defs.boxes['goal1'][0:3], - 'scale': defs.boxes['goal1'][6:9], - 'type': 'box', - 'materials': [self._score_region_material] - }))) + ba.newnode( + 'region', + attrs={ + 'position': defs.boxes['goal1'][0:3], + 'scale': defs.boxes['goal1'][6:9], + 'type': 'box', + 'materials': [self._score_region_material], + }, + ) + ) + ) self._score_regions.append( ba.NodeActor( - ba.newnode('region', - attrs={ - 'position': defs.boxes['goal2'][0:3], - 'scale': defs.boxes['goal2'][6:9], - 'type': 'box', - 'materials': [self._score_region_material] - }))) + ba.newnode( + 'region', + attrs={ + 'position': defs.boxes['goal2'][0:3], + 'scale': defs.boxes['goal2'][6:9], + 'type': 'box', + 'materials': [self._score_region_material], + }, + ) + ) + ) ba.playsound(self._chant_sound) def on_begin(self) -> None: # FIXME: Split this up a bit. # pylint: disable=too-many-statements from bastd.actor import controlsguide + super().on_begin() # Show controls help in kiosk mode. if ba.app.demo_mode or ba.app.arcade_mode: - controlsguide.ControlsGuide(delay=3.0, lifespan=10.0, - bright=True).autoretain() + controlsguide.ControlsGuide( + delay=3.0, lifespan=10.0, bright=True + ).autoretain() assert self.initialplayerinfos is not None abot: type[SpazBot] bbot: type[SpazBot] @@ -447,50 +496,64 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): if self._preset in ['rookie', 'rookie_easy']: self._exclude_powerups = ['curse'] self._have_tnt = False - abot = (BrawlerBotLite - if self._preset == 'rookie_easy' else BrawlerBot) + abot = ( + BrawlerBotLite if self._preset == 'rookie_easy' else BrawlerBot + ) self._bot_types_initial = [abot] * len(self.initialplayerinfos) - bbot = (BomberBotLite - if self._preset == 'rookie_easy' else BomberBot) - self._bot_types_7 = ( - [bbot] * (1 if len(self.initialplayerinfos) < 3 else 2)) - cbot = (BomberBot if self._preset == 'rookie_easy' else TriggerBot) - self._bot_types_14 = ( - [cbot] * (1 if len(self.initialplayerinfos) < 3 else 2)) + bbot = BomberBotLite if self._preset == 'rookie_easy' else BomberBot + self._bot_types_7 = [bbot] * ( + 1 if len(self.initialplayerinfos) < 3 else 2 + ) + cbot = BomberBot if self._preset == 'rookie_easy' else TriggerBot + self._bot_types_14 = [cbot] * ( + 1 if len(self.initialplayerinfos) < 3 else 2 + ) elif self._preset == 'tournament': self._exclude_powerups = [] self._have_tnt = True - self._bot_types_initial = ( - [BrawlerBot] * (1 if len(self.initialplayerinfos) < 2 else 2)) - self._bot_types_7 = ( - [TriggerBot] * (1 if len(self.initialplayerinfos) < 3 else 2)) - self._bot_types_14 = ( - [ChargerBot] * (1 if len(self.initialplayerinfos) < 4 else 2)) + self._bot_types_initial = [BrawlerBot] * ( + 1 if len(self.initialplayerinfos) < 2 else 2 + ) + self._bot_types_7 = [TriggerBot] * ( + 1 if len(self.initialplayerinfos) < 3 else 2 + ) + self._bot_types_14 = [ChargerBot] * ( + 1 if len(self.initialplayerinfos) < 4 else 2 + ) elif self._preset in ['pro', 'pro_easy', 'tournament_pro']: self._exclude_powerups = ['curse'] self._have_tnt = True self._bot_types_initial = [ChargerBot] * len( - self.initialplayerinfos) - abot = (BrawlerBot if self._preset == 'pro' else BrawlerBotLite) + self.initialplayerinfos + ) + abot = BrawlerBot if self._preset == 'pro' else BrawlerBotLite typed_bot_list: list[type[SpazBot]] = [] self._bot_types_7 = ( - typed_bot_list + [abot] + [BomberBot] * - (1 if len(self.initialplayerinfos) < 3 else 2)) - bbot = (TriggerBotPro if self._preset == 'pro' else TriggerBot) - self._bot_types_14 = ( - [bbot] * (1 if len(self.initialplayerinfos) < 3 else 2)) + typed_bot_list + + [abot] + + [BomberBot] * (1 if len(self.initialplayerinfos) < 3 else 2) + ) + bbot = TriggerBotPro if self._preset == 'pro' else TriggerBot + self._bot_types_14 = [bbot] * ( + 1 if len(self.initialplayerinfos) < 3 else 2 + ) elif self._preset in ['uber', 'uber_easy']: self._exclude_powerups = [] self._have_tnt = True - abot = (BrawlerBotPro if self._preset == 'uber' else BrawlerBot) - bbot = (TriggerBotPro if self._preset == 'uber' else TriggerBot) + abot = BrawlerBotPro if self._preset == 'uber' else BrawlerBot + bbot = TriggerBotPro if self._preset == 'uber' else TriggerBot typed_bot_list_2: list[type[SpazBot]] = [] - self._bot_types_initial = (typed_bot_list_2 + [StickyBot] + - [abot] * len(self.initialplayerinfos)) - self._bot_types_7 = ( - [bbot] * (1 if len(self.initialplayerinfos) < 3 else 2)) - self._bot_types_14 = ( - [ExplodeyBot] * (1 if len(self.initialplayerinfos) < 3 else 2)) + self._bot_types_initial = ( + typed_bot_list_2 + + [StickyBot] + + [abot] * len(self.initialplayerinfos) + ) + self._bot_types_7 = [bbot] * ( + 1 if len(self.initialplayerinfos) < 3 else 2 + ) + self._bot_types_14 = [ExplodeyBot] * ( + 1 if len(self.initialplayerinfos) < 3 else 2 + ) else: raise Exception() @@ -502,9 +565,9 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): # Make a bogus team for our bots. bad_team_name = self.get_team_display_string('Bad Guys') self._bot_team = Team() - self._bot_team.manual_init(team_id=1, - name=bad_team_name, - color=(0.5, 0.4, 0.4)) + self._bot_team.manual_init( + team_id=1, name=bad_team_name, color=(0.5, 0.4, 0.4) + ) for team in [self.teams[0], self._bot_team]: team.score = 0 @@ -516,26 +579,32 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): assert isinstance(starttime_ms, int) self._starttime_ms = starttime_ms self._time_text = ba.NodeActor( - ba.newnode('text', - attrs={ - 'v_attach': 'top', - 'h_attach': 'center', - 'h_align': 'center', - 'color': (1, 1, 0.5, 1), - 'flatness': 0.5, - 'shadow': 0.5, - 'position': (0, -50), - 'scale': 1.3, - 'text': '' - })) + ba.newnode( + 'text', + attrs={ + 'v_attach': 'top', + 'h_attach': 'center', + 'h_align': 'center', + 'color': (1, 1, 0.5, 1), + 'flatness': 0.5, + 'shadow': 0.5, + 'position': (0, -50), + 'scale': 1.3, + 'text': '', + }, + ) + ) self._time_text_input = ba.NodeActor( - ba.newnode('timedisplay', attrs={'showsubseconds': True})) - self.globalsnode.connectattr('time', self._time_text_input.node, - 'time2') + ba.newnode('timedisplay', attrs={'showsubseconds': True}) + ) + self.globalsnode.connectattr( + 'time', self._time_text_input.node, 'time2' + ) assert self._time_text_input.node assert self._time_text.node - self._time_text_input.node.connectattr('output', self._time_text.node, - 'text') + self._time_text_input.node.connectattr( + 'output', self._time_text.node, 'text' + ) # Our TNT spawner (if applicable). if self._have_tnt: @@ -554,15 +623,17 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): # We want to move to the left by default. spaz.target_point_default = ba.Vec3(0, 0, 0) - def _spawn_bot(self, - spaz_type: type[SpazBot], - immediate: bool = False) -> None: + def _spawn_bot( + self, spaz_type: type[SpazBot], immediate: bool = False + ) -> None: assert self._bot_team is not None pos = self.map.get_start_position(self._bot_team.id) - self._bots.spawn_bot(spaz_type, - pos=pos, - spawn_time=0.001 if immediate else 3.0, - on_spawn_call=self._on_bot_spawn) + self._bots.spawn_bot( + spaz_type, + pos=pos, + spawn_time=0.001 if immediate else 3.0, + on_spawn_call=self._on_bot_spawn, + ) def _update_bots(self) -> None: bots = self._bots.get_living_bots() @@ -581,8 +652,10 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): for player in self.players: if player.actor: assert isinstance(player.actor, PlayerSpaz) - if (player.actor.is_alive() and player.actor.node.hold_node - == self._flag.node): + if ( + player.actor.is_alive() + and player.actor.node.hold_node == self._flag.node + ): return flagpos = ba.Vec3(self._flag.node.position) @@ -601,41 +674,52 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): if closest_bot is not None: closest_bot.target_flag = self._flag - def _drop_powerup(self, - index: int, - poweruptype: str | None = None) -> None: + def _drop_powerup(self, index: int, poweruptype: str | None = None) -> None: if poweruptype is None: - poweruptype = (PowerupBoxFactory.get().get_random_powerup_type( - excludetypes=self._exclude_powerups)) - PowerupBox(position=self.map.powerup_spawn_points[index], - poweruptype=poweruptype).autoretain() + poweruptype = PowerupBoxFactory.get().get_random_powerup_type( + excludetypes=self._exclude_powerups + ) + PowerupBox( + position=self.map.powerup_spawn_points[index], + poweruptype=poweruptype, + ).autoretain() def _start_powerup_drops(self) -> None: - self._powerup_drop_timer = ba.Timer(3.0, - self._drop_powerups, - repeat=True) + self._powerup_drop_timer = ba.Timer( + 3.0, self._drop_powerups, repeat=True + ) - def _drop_powerups(self, - standard_points: bool = False, - poweruptype: str | None = None) -> None: + def _drop_powerups( + self, standard_points: bool = False, poweruptype: str | None = None + ) -> None: """Generic powerup drop.""" if standard_points: spawnpoints = self.map.powerup_spawn_points for i, _point in enumerate(spawnpoints): - ba.timer(1.0 + i * 0.5, - ba.Call(self._drop_powerup, i, poweruptype)) + ba.timer( + 1.0 + i * 0.5, ba.Call(self._drop_powerup, i, poweruptype) + ) else: - point = (self._powerup_center[0] + random.uniform( - -1.0 * self._powerup_spread[0], 1.0 * self._powerup_spread[0]), - self._powerup_center[1], - self._powerup_center[2] + random.uniform( - -self._powerup_spread[1], self._powerup_spread[1])) + point = ( + self._powerup_center[0] + + random.uniform( + -1.0 * self._powerup_spread[0], + 1.0 * self._powerup_spread[0], + ), + self._powerup_center[1], + self._powerup_center[2] + + random.uniform( + -self._powerup_spread[1], self._powerup_spread[1] + ), + ) # Drop one random one somewhere. PowerupBox( position=point, poweruptype=PowerupBoxFactory.get().get_random_powerup_type( - excludetypes=self._exclude_powerups)).autoretain() + excludetypes=self._exclude_powerups + ), + ).autoretain() def _kill_flag(self) -> None: try: @@ -645,7 +729,7 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): ba.print_exception('Error in _kill_flag.') def _handle_score(self) -> None: - """ a point has been scored """ + """a point has been scored""" # FIXME tidy this up # pylint: disable=too-many-branches @@ -671,8 +755,7 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): if i == 0: for player in team.players: if player.actor: - player.actor.handlemessage( - ba.CelebrateMessage(2.0)) + player.actor.handlemessage(ba.CelebrateMessage(2.0)) else: self._bots.celebrate(2.0) @@ -699,12 +782,14 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): ba.timer(0.2, self._kill_flag) self.update_scores() - light = ba.newnode('light', - attrs={ - 'position': ba.getcollision().position, - 'height_attenuated': False, - 'color': (1, 0, 0) - }) + light = ba.newnode( + 'light', + attrs={ + 'position': ba.getcollision().position, + 'height_attenuated': False, + 'color': (1, 0, 0), + }, + ) ba.animate(light, 'intensity', {0: 0, 0.5: 1, 1.0: 0}, loop=True) ba.timer(1.0, light.delete) if i == 0: @@ -723,7 +808,7 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): self.update_scores() def update_scores(self) -> None: - """ update scoreboard and check for winners """ + """update scoreboard and check for winners""" # FIXME: tidy this up # pylint: disable=too-many-nested-blocks have_scoring_team = False @@ -743,41 +828,54 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): # Completion achievements. assert self._bot_team is not None if self._preset in ['rookie', 'rookie_easy']: - self._award_achievement('Rookie Football Victory', - sound=False) + self._award_achievement( + 'Rookie Football Victory', sound=False + ) if self._bot_team.score == 0: self._award_achievement( - 'Rookie Football Shutout', sound=False) + 'Rookie Football Shutout', sound=False + ) elif self._preset in ['pro', 'pro_easy']: - self._award_achievement('Pro Football Victory', - sound=False) - if self._bot_team.score == 0: - self._award_achievement('Pro Football Shutout', - sound=False) - elif self._preset in ['uber', 'uber_easy']: - self._award_achievement('Uber Football Victory', - sound=False) + self._award_achievement( + 'Pro Football Victory', sound=False + ) if self._bot_team.score == 0: self._award_achievement( - 'Uber Football Shutout', sound=False) - if (not self._player_has_dropped_bomb - and not self._player_has_punched): - self._award_achievement('Got the Moves', - sound=False) + 'Pro Football Shutout', sound=False + ) + elif self._preset in ['uber', 'uber_easy']: + self._award_achievement( + 'Uber Football Victory', sound=False + ) + if self._bot_team.score == 0: + self._award_achievement( + 'Uber Football Shutout', sound=False + ) + if ( + not self._player_has_dropped_bomb + and not self._player_has_punched + ): + self._award_achievement( + 'Got the Moves', sound=False + ) self._bots.stop_moving() - self.show_zoom_message(ba.Lstr(resource='victoryText'), - scale=1.0, - duration=4.0) + self.show_zoom_message( + ba.Lstr(resource='victoryText'), + scale=1.0, + duration=4.0, + ) self.celebrate(10.0) assert self._starttime_ms is not None self._final_time_ms = int( - ba.time(timeformat=ba.TimeFormat.MILLISECONDS) - - self._starttime_ms) + ba.time(timeformat=ba.TimeFormat.MILLISECONDS) + - self._starttime_ms + ) self._time_text_timer = None - assert (self._time_text_input is not None - and self._time_text_input.node) - self._time_text_input.node.timemax = ( - self._final_time_ms) + assert ( + self._time_text_input is not None + and self._time_text_input.node + ) + self._time_text_input.node.timemax = self._final_time_ms # FIXME: Does this still need to be deferred? ba.pushcall(ba.Call(self.do_end, 'victory')) @@ -786,18 +884,22 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): """End the game with the specified outcome.""" if outcome == 'defeat': self.fade_to_red() - #assert self._final_time_ms is not None - scoreval = (None if outcome == 'defeat' else 5) - self.end(delay=3.0, - results={ - 'outcome': outcome, - 'score': scoreval, - 'score_order': 'decreasing', - 'playerinfos': self.initialplayerinfos - }) + assert self._final_time_ms is not None + scoreval = ( + None if outcome == 'defeat' else int(self._final_time_ms // 10) + ) + self.end( + delay=3.0, + results={ + 'outcome': outcome, + 'score': scoreval, + 'score_order': 'decreasing', + 'playerinfos': self.initialplayerinfos, + }, + ) def handlemessage(self, msg: Any) -> Any: - """ handle high-level game messages """ + """handle high-level game messages""" if isinstance(msg, ba.PlayerDiedMessage): # Augment standard behavior. super().handlemessage(msg) @@ -807,7 +909,8 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): assert self.initialplayerinfos is not None respawn_time = 2.0 + len(self.initialplayerinfos) * 1.0 player.respawn_timer = ba.Timer( - respawn_time, ba.Call(self.spawn_player_if_exists, player)) + respawn_time, ba.Call(self.spawn_player_if_exists, player) + ) player.respawn_icon = RespawnIcon(player, respawn_time) elif isinstance(msg, SpazBotDiedMessage): @@ -828,28 +931,29 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): assert isinstance(msg.flag, FootballFlag) msg.flag.respawn_timer = ba.Timer(3.0, self._spawn_flag) self._flag_respawn_light = ba.NodeActor( - ba.newnode('light', - attrs={ - 'position': self._flag_spawn_pos, - 'height_attenuated': False, - 'radius': 0.15, - 'color': (1.0, 1.0, 0.3) - })) + ba.newnode( + 'light', + attrs={ + 'position': self._flag_spawn_pos, + 'height_attenuated': False, + 'radius': 0.15, + 'color': (1.0, 1.0, 0.3), + }, + ) + ) assert self._flag_respawn_light.node - ba.animate(self._flag_respawn_light.node, - 'intensity', { - 0: 0, - 0.25: 0.15, - 0.5: 0 - }, - loop=True) + ba.animate( + self._flag_respawn_light.node, + 'intensity', + {0: 0, 0.25: 0.15, 0.5: 0}, + loop=True, + ) ba.timer(3.0, self._flag_respawn_light.node.delete) else: return super().handlemessage(msg) return None - def _handle_player_dropped_bomb(self, player: Spaz, - bomb: ba.Actor) -> None: + def _handle_player_dropped_bomb(self, player: Spaz, bomb: ba.Actor) -> None: del player, bomb # Unused. self._player_has_dropped_bomb = True @@ -858,9 +962,9 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): self._player_has_punched = True def spawn_player(self, player: Player) -> ba.Actor: - spaz = self.spawn_player_spaz(player, - position=self.map.get_start_position( - player.team.id)) + spaz = self.spawn_player_spaz( + player, position=self.map.get_start_position(player.team.id) + ) if self._preset in ['rookie_easy', 'pro_easy', 'uber_easy']: spaz.impact_scale = 0.25 spaz.add_dropped_bomb_callback(self._handle_player_dropped_bomb) @@ -868,12 +972,14 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): return spaz def _flash_flag_spawn(self) -> None: - light = ba.newnode('light', - attrs={ - 'position': self._flag_spawn_pos, - 'height_attenuated': False, - 'color': (1, 1, 0) - }) + light = ba.newnode( + 'light', + attrs={ + 'position': self._flag_spawn_pos, + 'height_attenuated': False, + 'color': (1, 1, 0), + }, + ) ba.animate(light, 'intensity', {0: 0, 0.25: 0.25, 0.5: 0}, loop=True) ba.timer(1.0, light.delete) diff --git a/dist/ba_data/python/bastd/game/hockey.py b/dist/ba_data/python/bastd/game/hockey.py index 11110e7..d476aaa 100644 --- a/dist/ba_data/python/bastd/game/hockey.py +++ b/dist/ba_data/python/bastd/game/hockey.py @@ -41,19 +41,21 @@ class Puck(ba.Actor): assert activity is not None assert isinstance(activity, HockeyGame) pmats = [shared.object_material, activity.puck_material] - self.node = ba.newnode('prop', - delegate=self, - attrs={ - 'model': activity.puck_model, - 'color_texture': activity.puck_tex, - 'body': 'puck', - 'reflection': 'soft', - 'reflection_scale': [0.2], - 'shadow_size': 1.0, - 'is_area_of_interest': True, - 'position': self._spawn_pos, - 'materials': pmats - }) + self.node = ba.newnode( + 'prop', + delegate=self, + attrs={ + 'model': activity.puck_model, + 'color_texture': activity.puck_tex, + 'body': 'puck', + 'reflection': 'soft', + 'reflection_scale': [0.2], + 'shadow_size': 1.0, + 'is_area_of_interest': True, + 'position': self._spawn_pos, + 'materials': pmats, + }, + ) ba.animate(self.node, 'model_scale', {0: 0, 0.2: 1.3, 0.26: 1}) def handlemessage(self, msg: Any) -> Any: @@ -73,11 +75,21 @@ class Puck(ba.Actor): assert self.node assert msg.force_direction is not None self.node.handlemessage( - 'impulse', msg.pos[0], msg.pos[1], msg.pos[2], msg.velocity[0], - msg.velocity[1], msg.velocity[2], 1.0 * msg.magnitude, - 1.0 * msg.velocity_magnitude, msg.radius, 0, - msg.force_direction[0], msg.force_direction[1], - msg.force_direction[2]) + 'impulse', + msg.pos[0], + msg.pos[1], + msg.pos[2], + msg.velocity[0], + msg.velocity[1], + msg.velocity[2], + 1.0 * msg.magnitude, + 1.0 * msg.velocity_magnitude, + msg.radius, + 0, + msg.force_direction[0], + msg.force_direction[1], + msg.force_direction[2], + ) # If this hit came from a player, log them as the last to touch us. s_player = msg.get_source_player(Player) @@ -161,12 +173,13 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]): self.puck_tex = ba.gettexture('puckColor') self._puck_sound = ba.getsound('metalHit') self.puck_material = ba.Material() - self.puck_material.add_actions(actions=(('modify_part_collision', - 'friction', 0.5))) - self.puck_material.add_actions(conditions=('they_have_material', - shared.pickup_material), - actions=('modify_part_collision', - 'collide', False)) + self.puck_material.add_actions( + actions=('modify_part_collision', 'friction', 0.5) + ) + self.puck_material.add_actions( + conditions=('they_have_material', shared.pickup_material), + actions=('modify_part_collision', 'collide', False), + ) self.puck_material.add_actions( conditions=( ('we_are_younger_than', 100), @@ -175,29 +188,37 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]): ), actions=('modify_node_collision', 'collide', False), ) - self.puck_material.add_actions(conditions=('they_have_material', - shared.footing_material), - actions=('impact_sound', - self._puck_sound, 0.2, 5)) + self.puck_material.add_actions( + conditions=('they_have_material', shared.footing_material), + actions=('impact_sound', self._puck_sound, 0.2, 5), + ) # Keep track of which player last touched the puck self.puck_material.add_actions( conditions=('they_have_material', shared.player_material), - actions=(('call', 'at_connect', - self._handle_puck_player_collide), )) + actions=(('call', 'at_connect', self._handle_puck_player_collide),), + ) # We want the puck to kill powerups; not get stopped by them self.puck_material.add_actions( - conditions=('they_have_material', - PowerupBoxFactory.get().powerup_material), - actions=(('modify_part_collision', 'physical', False), - ('message', 'their_node', 'at_connect', ba.DieMessage()))) + conditions=( + 'they_have_material', + PowerupBoxFactory.get().powerup_material, + ), + actions=( + ('modify_part_collision', 'physical', False), + ('message', 'their_node', 'at_connect', ba.DieMessage()), + ), + ) self._score_region_material = ba.Material() self._score_region_material.add_actions( conditions=('they_have_material', self.puck_material), - actions=(('modify_part_collision', 'collide', - True), ('modify_part_collision', 'physical', False), - ('call', 'at_connect', self._handle_score))) + actions=( + ('modify_part_collision', 'collide', True), + ('modify_part_collision', 'physical', False), + ('call', 'at_connect', self._handle_score), + ), + ) self._puck_spawn_pos: Sequence[float] | None = None self._score_regions: list[ba.NodeActor] | None = None self._puck: Puck | None = None @@ -205,8 +226,9 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]): self._time_limit = float(settings['Time Limit']) self._epic_mode = bool(settings['Epic Mode']) self.slow_motion = self._epic_mode - self.default_music = (ba.MusicType.EPIC - if self._epic_mode else ba.MusicType.HOCKEY) + self.default_music = ( + ba.MusicType.EPIC if self._epic_mode else ba.MusicType.HOCKEY + ) def get_instance_description(self) -> str | Sequence: if self._score_to_win == 1: @@ -231,22 +253,30 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]): self._score_regions = [] self._score_regions.append( ba.NodeActor( - ba.newnode('region', - attrs={ - 'position': defs.boxes['goal1'][0:3], - 'scale': defs.boxes['goal1'][6:9], - 'type': 'box', - 'materials': [self._score_region_material] - }))) + ba.newnode( + 'region', + attrs={ + 'position': defs.boxes['goal1'][0:3], + 'scale': defs.boxes['goal1'][6:9], + 'type': 'box', + 'materials': [self._score_region_material], + }, + ) + ) + ) self._score_regions.append( ba.NodeActor( - ba.newnode('region', - attrs={ - 'position': defs.boxes['goal2'][0:3], - 'scale': defs.boxes['goal2'][6:9], - 'type': 'box', - 'materials': [self._score_region_material] - }))) + ba.newnode( + 'region', + attrs={ + 'position': defs.boxes['goal2'][0:3], + 'scale': defs.boxes['goal2'][6:9], + 'type': 'box', + 'materials': [self._score_region_material], + }, + ) + ) + ) self._update_scoreboard() ba.playsound(self._chant_sound) @@ -257,9 +287,9 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]): collision = ba.getcollision() try: puck = collision.sourcenode.getdelegate(Puck, True) - player = collision.opposingnode.getdelegate(PlayerSpaz, - True).getplayer( - Player, True) + player = collision.opposingnode.getdelegate( + PlayerSpaz, True + ).getplayer(Player, True) except ba.NotFoundError: return @@ -297,12 +327,15 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]): # If we've got the player from the scoring team that last # touched us, give them points. - if (scoring_team.id in self._puck.last_players_to_touch - and self._puck.last_players_to_touch[scoring_team.id]): + if ( + scoring_team.id in self._puck.last_players_to_touch + and self._puck.last_players_to_touch[scoring_team.id] + ): self.stats.player_scored( self._puck.last_players_to_touch[scoring_team.id], 100, - big_message=True) + big_message=True, + ) # End game if we won. if team.score >= self._score_to_win: @@ -316,12 +349,14 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]): # Kill the puck (it'll respawn itself shortly). ba.timer(1.0, self._kill_puck) - light = ba.newnode('light', - attrs={ - 'position': ba.getcollision().position, - 'height_attenuated': False, - 'color': (1, 0, 0) - }) + light = ba.newnode( + 'light', + attrs={ + 'position': ba.getcollision().position, + 'height_attenuated': False, + 'color': (1, 0, 0), + }, + ) ba.animate(light, 'intensity', {0: 0, 0.5: 1, 1.0: 0}, loop=True) ba.timer(1.0, light.delete) @@ -355,12 +390,14 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]): super().handlemessage(msg) def _flash_puck_spawn(self) -> None: - light = ba.newnode('light', - attrs={ - 'position': self._puck_spawn_pos, - 'height_attenuated': False, - 'color': (1, 0, 0) - }) + light = ba.newnode( + 'light', + attrs={ + 'position': self._puck_spawn_pos, + 'height_attenuated': False, + 'color': (1, 0, 0), + }, + ) ba.animate(light, 'intensity', {0.0: 0, 0.25: 1, 0.5: 0}, loop=True) ba.timer(1.0, light.delete) diff --git a/dist/ba_data/python/bastd/game/keepaway.py b/dist/ba_data/python/bastd/game/keepaway.py index 3d3477e..af7396e 100644 --- a/dist/ba_data/python/bastd/game/keepaway.py +++ b/dist/ba_data/python/bastd/game/keepaway.py @@ -13,8 +13,12 @@ from typing import TYPE_CHECKING import ba from bastd.actor.playerspaz import PlayerSpaz from bastd.actor.scoreboard import Scoreboard -from bastd.actor.flag import (Flag, FlagDroppedMessage, FlagDiedMessage, - FlagPickedUpMessage) +from bastd.actor.flag import ( + Flag, + FlagDroppedMessage, + FlagDiedMessage, + FlagPickedUpMessage, +) if TYPE_CHECKING: from typing import Any, Sequence @@ -22,6 +26,7 @@ if TYPE_CHECKING: class FlagState(Enum): """States our single flag can be in.""" + NEW = 0 UNCONTESTED = 1 CONTESTED = 2 @@ -82,8 +87,9 @@ class KeepAwayGame(ba.TeamGameActivity[Player, Team]): @classmethod def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool: - return (issubclass(sessiontype, ba.DualTeamSession) - or issubclass(sessiontype, ba.FreeForAllSession)) + return issubclass(sessiontype, ba.DualTeamSession) or issubclass( + sessiontype, ba.FreeForAllSession + ) @classmethod def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]: @@ -104,7 +110,7 @@ class KeepAwayGame(ba.TeamGameActivity[Player, Team]): 4: ba.getsound('announceFour'), 3: ba.getsound('announceThree'), 2: ba.getsound('announceTwo'), - 1: ba.getsound('announceOne') + 1: ba.getsound('announceOne'), } self._flag_spawn_pos: Sequence[float] | None = None self._update_timer: ba.Timer | None = None @@ -117,8 +123,9 @@ class KeepAwayGame(ba.TeamGameActivity[Player, Team]): self._time_limit = float(settings['Time Limit']) self._epic_mode = bool(settings['Epic Mode']) self.slow_motion = self._epic_mode - self.default_music = (ba.MusicType.EPIC - if self._epic_mode else ba.MusicType.KEEP_AWAY) + self.default_music = ( + ba.MusicType.EPIC if self._epic_mode else ba.MusicType.KEEP_AWAY + ) def get_instance_description(self) -> str | Sequence: return 'Carry the flag for ${ARG1} seconds.', self._hold_time @@ -149,10 +156,9 @@ class KeepAwayGame(ba.TeamGameActivity[Player, Team]): for player in self._holding_players: if player: assert self.stats - self.stats.player_scored(player, - 3, - screenmessage=False, - display=False) + self.stats.player_scored( + player, 3, screenmessage=False, display=False + ) scoreteam = self._scoring_team @@ -189,10 +195,14 @@ class KeepAwayGame(ba.TeamGameActivity[Player, Team]): holdingflag = False try: assert isinstance(player.actor, (PlayerSpaz, type(None))) - if (player.actor and player.actor.node - and player.actor.node.hold_node): + if ( + player.actor + and player.actor.node + and player.actor.node.hold_node + ): holdingflag = ( - player.actor.node.hold_node.getnodetype() == 'flag') + player.actor.node.hold_node.getnodetype() == 'flag' + ) except Exception: ba.print_exception('Error checking hold flag.') if holdingflag: @@ -230,34 +240,33 @@ class KeepAwayGame(ba.TeamGameActivity[Player, Team]): assert self._flag_spawn_pos is not None self._flag = Flag(dropped_timeout=20, position=self._flag_spawn_pos) self._flag_state = FlagState.NEW - self._flag_light = ba.newnode('light', - owner=self._flag.node, - attrs={ - 'intensity': 0.2, - 'radius': 0.3, - 'color': (0.2, 0.2, 0.2) - }) + self._flag_light = ba.newnode( + 'light', + owner=self._flag.node, + attrs={'intensity': 0.2, 'radius': 0.3, 'color': (0.2, 0.2, 0.2)}, + ) assert self._flag.node self._flag.node.connectattr('position', self._flag_light, 'position') self._update_flag_state() def _flash_flag_spawn(self) -> None: - light = ba.newnode('light', - attrs={ - 'position': self._flag_spawn_pos, - 'color': (1, 1, 1), - 'radius': 0.3, - 'height_attenuated': False - }) + light = ba.newnode( + 'light', + attrs={ + 'position': self._flag_spawn_pos, + 'color': (1, 1, 1), + 'radius': 0.3, + 'height_attenuated': False, + }, + ) ba.animate(light, 'intensity', {0.0: 0, 0.25: 0.5, 0.5: 0}, loop=True) ba.timer(1.0, light.delete) def _update_scoreboard(self) -> None: for team in self.teams: - self._scoreboard.set_team_value(team, - team.timeremaining, - self._hold_time, - countdown=True) + self._scoreboard.set_team_value( + team, team.timeremaining, self._hold_time, countdown=True + ) def handlemessage(self, msg: Any) -> Any: if isinstance(msg, ba.PlayerDiedMessage): diff --git a/dist/ba_data/python/bastd/game/kingofthehill.py b/dist/ba_data/python/bastd/game/kingofthehill.py index 677c9fd..f70d13f 100644 --- a/dist/ba_data/python/bastd/game/kingofthehill.py +++ b/dist/ba_data/python/bastd/game/kingofthehill.py @@ -23,6 +23,7 @@ if TYPE_CHECKING: class FlagState(Enum): """States our single flag can be in.""" + NEW = 0 UNCONTESTED = 1 CONTESTED = 2 @@ -107,7 +108,7 @@ class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]): 4: ba.getsound('announceFour'), 3: ba.getsound('announceThree'), 2: ba.getsound('announceTwo'), - 1: ba.getsound('announceOne') + 1: ba.getsound('announceOne'), } self._flag_pos: Sequence[float] | None = None self._flag_state: FlagState | None = None @@ -123,16 +124,24 @@ class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]): actions=( ('modify_part_collision', 'collide', True), ('modify_part_collision', 'physical', False), - ('call', 'at_connect', - ba.Call(self._handle_player_flag_region_collide, True)), - ('call', 'at_disconnect', - ba.Call(self._handle_player_flag_region_collide, False)), - )) + ( + 'call', + 'at_connect', + ba.Call(self._handle_player_flag_region_collide, True), + ), + ( + 'call', + 'at_disconnect', + ba.Call(self._handle_player_flag_region_collide, False), + ), + ), + ) # Base class overrides. self.slow_motion = self._epic_mode - self.default_music = (ba.MusicType.EPIC - if self._epic_mode else ba.MusicType.SCARY) + self.default_music = ( + ba.MusicType.EPIC if self._epic_mode else ba.MusicType.SCARY + ) def get_instance_description(self) -> str | Sequence: return 'Secure the flag for ${ARG1} seconds.', self._hold_time @@ -152,26 +161,30 @@ class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]): ba.timer(1.0, self._tick, repeat=True) self._flag_state = FlagState.NEW Flag.project_stand(self._flag_pos) - self._flag = Flag(position=self._flag_pos, - touchable=False, - color=(1, 1, 1)) - self._flag_light = ba.newnode('light', - attrs={ - 'position': self._flag_pos, - 'intensity': 0.2, - 'height_attenuated': False, - 'radius': 0.4, - 'color': (0.2, 0.2, 0.2) - }) + self._flag = Flag( + position=self._flag_pos, touchable=False, color=(1, 1, 1) + ) + self._flag_light = ba.newnode( + 'light', + attrs={ + 'position': self._flag_pos, + 'intensity': 0.2, + 'height_attenuated': False, + 'radius': 0.4, + 'color': (0.2, 0.2, 0.2), + }, + ) # Flag region. flagmats = [self._flag_region_material, shared.region_material] - ba.newnode('region', - attrs={ - 'position': self._flag_pos, - 'scale': (1.8, 1.8, 1.8), - 'type': 'sphere', - 'materials': flagmats - }) + ba.newnode( + 'region', + attrs={ + 'position': self._flag_pos, + 'scale': (1.8, 1.8, 1.8), + 'type': 'sphere', + 'materials': flagmats, + }, + ) self._update_flag_state() def _tick(self) -> None: @@ -180,10 +193,9 @@ class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]): # Give holding players points. for player in self.players: if player.time_at_flag > 0: - self.stats.player_scored(player, - 3, - screenmessage=False, - display=False) + self.stats.player_scored( + player, 3, screenmessage=False, display=False + ) if self._scoring_team is None: scoring_team = None else: @@ -193,8 +205,9 @@ class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]): if scoring_team.time_remaining > 0: ba.playsound(self._tick_sound) - scoring_team.time_remaining = max(0, - scoring_team.time_remaining - 1) + scoring_team.time_remaining = max( + 0, scoring_team.time_remaining - 1 + ) self._update_scoreboard() if scoring_team.time_remaining > 0: assert self._flag is not None @@ -216,8 +229,9 @@ class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]): self.end(results=results, announce_delay=0) def _update_flag_state(self) -> None: - holding_teams = set(player.team for player in self.players - if player.time_at_flag) + holding_teams = set( + player.team for player in self.players if player.time_at_flag + ) prev_state = self._flag_state assert self._flag_light assert self._flag is not None @@ -264,10 +278,9 @@ class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]): def _update_scoreboard(self) -> None: for team in self.teams: - self._scoreboard.set_team_value(team, - team.time_remaining, - self._hold_time, - countdown=True) + self._scoreboard.set_team_value( + team, team.time_remaining, self._hold_time, countdown=True + ) def handlemessage(self, msg: Any) -> Any: if isinstance(msg, ba.PlayerDiedMessage): diff --git a/dist/ba_data/python/bastd/game/meteorshower.py b/dist/ba_data/python/bastd/game/meteorshower.py index 2ca2012..754fa9a 100644 --- a/dist/ba_data/python/bastd/game/meteorshower.py +++ b/dist/ba_data/python/bastd/game/meteorshower.py @@ -37,9 +37,9 @@ class MeteorShowerGame(ba.TeamGameActivity[Player, Team]): name = 'Meteor Shower' description = 'Dodge the falling bombs.' available_settings = [ba.BoolSetting('Epic Mode', default=False)] - scoreconfig = ba.ScoreConfig(label='Survived', - scoretype=ba.ScoreType.MILLISECONDS, - version='B') + scoreconfig = ba.ScoreConfig( + label='Survived', scoretype=ba.ScoreType.MILLISECONDS, version='B' + ) # Print messages when players die (since its meaningful in this game). announce_player_deaths = True @@ -56,9 +56,11 @@ class MeteorShowerGame(ba.TeamGameActivity[Player, Team]): # We support teams, free-for-all, and co-op sessions. @classmethod def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool: - return (issubclass(sessiontype, ba.DualTeamSession) - or issubclass(sessiontype, ba.FreeForAllSession) - or issubclass(sessiontype, ba.CoopSession)) + return ( + issubclass(sessiontype, ba.DualTeamSession) + or issubclass(sessiontype, ba.FreeForAllSession) + or issubclass(sessiontype, ba.CoopSession) + ) def __init__(self, settings: dict): super().__init__(settings) @@ -69,8 +71,9 @@ class MeteorShowerGame(ba.TeamGameActivity[Player, Team]): self._timer: OnScreenTimer | None = None # Some base class overrides: - self.default_music = (ba.MusicType.EPIC - if self._epic_mode else ba.MusicType.SURVIVAL) + self.default_music = ( + ba.MusicType.EPIC if self._epic_mode else ba.MusicType.SURVIVAL + ) if self._epic_mode: self.slow_motion = True @@ -110,9 +113,9 @@ class MeteorShowerGame(ba.TeamGameActivity[Player, Team]): # Let's reconnect this player's controls to this # spaz but *without* the ability to attack or pick stuff up. - spaz.connect_controls_to_player(enable_punch=False, - enable_bomb=False, - enable_pickup=False) + spaz.connect_controls_to_player( + enable_punch=False, enable_bomb=False, enable_pickup=False + ) # Also lets have them make some noise when they die. spaz.play_big_death_sound = True @@ -168,8 +171,10 @@ class MeteorShowerGame(ba.TeamGameActivity[Player, Team]): self.end_game() def _set_meteor_timer(self) -> None: - ba.timer((1.0 + 0.2 * random.random()) * self._meteor_time, - self._drop_bomb_cluster) + ba.timer( + (1.0 + 0.2 * random.random()) * self._meteor_time, + self._drop_bomb_cluster, + ) def _drop_bomb_cluster(self) -> None: @@ -187,17 +192,24 @@ class MeteorShowerGame(ba.TeamGameActivity[Player, Team]): for _i in range(random.randrange(1, 3)): # Drop them somewhere within our bounds with velocity pointing # toward the opposite side. - pos = (-7.3 + 15.3 * random.random(), 11, - -5.57 + 2.1 * random.random()) - dropdir = (-1.0 if pos[0] > 0 else 1.0) - vel = ((-5.0 + random.random() * 30.0) * dropdir, - random.uniform(-3.066, -4.12), 0) + pos = ( + -7.3 + 15.3 * random.random(), + 11, + -5.57 + 2.1 * random.random(), + ) + dropdir = -1.0 if pos[0] > 0 else 1.0 + vel = ( + (-5.0 + random.random() * 30.0) * dropdir, + random.uniform(-3.066, -4.12), + 0, + ) ba.timer(delay, ba.Call(self._drop_bomb, pos, vel)) delay += 0.1 self._set_meteor_timer() - def _drop_bomb(self, position: Sequence[float], - velocity: Sequence[float]) -> None: + def _drop_bomb( + self, position: Sequence[float], velocity: Sequence[float] + ) -> None: Bomb(position=position, velocity=velocity).autoretain() def _decrement_meteor_time(self) -> None: @@ -247,8 +259,7 @@ class MeteorShowerGame(ba.TeamGameActivity[Player, Team]): longest_life = 0.0 for player in team.players: assert player.death_time is not None - longest_life = max(longest_life, - player.death_time - start_time) + longest_life = max(longest_life, player.death_time - start_time) # Submit the score value in milliseconds. results.set_team_score(team, int(1000.0 * longest_life)) diff --git a/dist/ba_data/python/bastd/game/ninjafight.py b/dist/ba_data/python/bastd/game/ninjafight.py index 3540263..343ac1e 100644 --- a/dist/ba_data/python/bastd/game/ninjafight.py +++ b/dist/ba_data/python/bastd/game/ninjafight.py @@ -35,9 +35,9 @@ class NinjaFightGame(ba.TeamGameActivity[Player, Team]): name = 'Ninja Fight' description = 'How fast can you defeat the ninjas?' - scoreconfig = ba.ScoreConfig(label='Time', - scoretype=ba.ScoreType.MILLISECONDS, - lower_is_better=True) + scoreconfig = ba.ScoreConfig( + label='Time', scoretype=ba.ScoreType.MILLISECONDS, lower_is_better=True + ) default_music = ba.MusicType.TO_THE_DEATH @classmethod @@ -77,36 +77,57 @@ class NinjaFightGame(ba.TeamGameActivity[Player, Team]): # Spawn some baddies. ba.timer( - 1.0, lambda: self._bots.spawn_bot( - ChargerBot, pos=(3, 3, -2), spawn_time=3.0)) + 1.0, + lambda: self._bots.spawn_bot( + ChargerBot, pos=(3, 3, -2), spawn_time=3.0 + ), + ) ba.timer( - 2.0, lambda: self._bots.spawn_bot( - ChargerBot, pos=(-3, 3, -2), spawn_time=3.0)) + 2.0, + lambda: self._bots.spawn_bot( + ChargerBot, pos=(-3, 3, -2), spawn_time=3.0 + ), + ) ba.timer( - 3.0, lambda: self._bots.spawn_bot( - ChargerBot, pos=(5, 3, -2), spawn_time=3.0)) + 3.0, + lambda: self._bots.spawn_bot( + ChargerBot, pos=(5, 3, -2), spawn_time=3.0 + ), + ) ba.timer( - 4.0, lambda: self._bots.spawn_bot( - ChargerBot, pos=(-5, 3, -2), spawn_time=3.0)) + 4.0, + lambda: self._bots.spawn_bot( + ChargerBot, pos=(-5, 3, -2), spawn_time=3.0 + ), + ) # Add some extras for multiplayer or pro mode. assert self.initialplayerinfos is not None if len(self.initialplayerinfos) > 2 or is_pro: ba.timer( - 5.0, lambda: self._bots.spawn_bot( - ChargerBot, pos=(0, 3, -5), spawn_time=3.0)) + 5.0, + lambda: self._bots.spawn_bot( + ChargerBot, pos=(0, 3, -5), spawn_time=3.0 + ), + ) if len(self.initialplayerinfos) > 3 or is_pro: ba.timer( - 6.0, lambda: self._bots.spawn_bot( - ChargerBot, pos=(0, 3, 1), spawn_time=3.0)) + 6.0, + lambda: self._bots.spawn_bot( + ChargerBot, pos=(0, 3, 1), spawn_time=3.0 + ), + ) # Called for each spawning player. def spawn_player(self, player: Player) -> ba.Actor: # Let's spawn close to the center. spawn_center = (0, 3, -2) - pos = (spawn_center[0] + random.uniform(-1.5, 1.5), spawn_center[1], - spawn_center[2] + random.uniform(-1.5, 1.5)) + pos = ( + spawn_center[0] + random.uniform(-1.5, 1.5), + spawn_center[1], + spawn_center[2] + random.uniform(-1.5, 1.5), + ) return self.spawn_player_spaz(player, position=pos) def _check_if_won(self) -> None: diff --git a/dist/ba_data/python/bastd/game/onslaught.py b/dist/ba_data/python/bastd/game/onslaught.py index 9064b79..033a671 100644 --- a/dist/ba_data/python/bastd/game/onslaught.py +++ b/dist/ba_data/python/bastd/game/onslaught.py @@ -24,11 +24,27 @@ from bastd.actor.scoreboard import Scoreboard from bastd.actor.controlsguide import ControlsGuide from bastd.actor.powerupbox import PowerupBox, PowerupBoxFactory from bastd.actor.spazbot import ( - SpazBotDiedMessage, SpazBotSet, ChargerBot, StickyBot, BomberBot, - BomberBotLite, BrawlerBot, BrawlerBotLite, TriggerBot, BomberBotStaticLite, - TriggerBotStatic, BomberBotProStatic, TriggerBotPro, ExplodeyBot, - BrawlerBotProShielded, ChargerBotProShielded, BomberBotPro, - TriggerBotProShielded, BrawlerBotPro, BomberBotProShielded) + SpazBotDiedMessage, + SpazBotSet, + ChargerBot, + StickyBot, + BomberBot, + BomberBotLite, + BrawlerBot, + BrawlerBotLite, + TriggerBot, + BomberBotStaticLite, + TriggerBotStatic, + BomberBotProStatic, + TriggerBotPro, + ExplodeyBot, + BrawlerBotProShielded, + ChargerBotProShielded, + BomberBotPro, + TriggerBotProShielded, + BrawlerBotPro, + BomberBotProShielded, +) if TYPE_CHECKING: from typing import Any, Sequence @@ -38,6 +54,7 @@ if TYPE_CHECKING: @dataclass class Wave: """A wave of enemies.""" + entries: list[Spawn | Spacing | Delay | None] base_angle: float = 0.0 @@ -45,6 +62,7 @@ class Wave: @dataclass class Spawn: """A bot spawn event in a wave.""" + bottype: type[SpazBot] | str point: Point | None = None spacing: float = 5.0 @@ -53,17 +71,20 @@ class Spawn: @dataclass class Spacing: """Empty space in a wave.""" + spacing: float = 5.0 @dataclass class Delay: """A delay between events in a wave.""" + duration: float class Preset(Enum): """Game presets we support.""" + TRAINING = 'training' TRAINING_EASY = 'training_easy' ROOKIE = 'rookie' @@ -79,6 +100,7 @@ class Preset(Enum): @unique class Point(Enum): """Points on the map we can spawn at.""" + LEFT_UPPER_MORE = 'bot_spawn_left_upper_more' LEFT_UPPER = 'bot_spawn_left_upper' TURRET_TOP_RIGHT = 'bot_spawn_turret_top_right' @@ -134,7 +156,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): 'It\'s easier to win with a friend or two helping.', 'If you stay in one place, you\'re toast. Run and dodge to survive..', 'Practice using your momentum to throw bombs more accurately.', - 'Your punches do much more damage if you are running or spinning.' + 'Your punches do much more damage if you are running or spinning.', ] # Show messages when players die since it matters here. @@ -144,8 +166,12 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): self._preset = Preset(settings.get('preset', 'training')) if self._preset in { - Preset.TRAINING, Preset.TRAINING_EASY, Preset.PRO, - Preset.PRO_EASY, Preset.ENDLESS, Preset.ENDLESS_TOURNAMENT + Preset.TRAINING, + Preset.TRAINING_EASY, + Preset.PRO, + Preset.PRO_EASY, + Preset.ENDLESS, + Preset.ENDLESS_TOURNAMENT, }: settings['map'] = 'Doom Shroom' else: @@ -209,7 +235,8 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): ba.GameTip( 'Land-mines are a good way to stop speedy enemies.', icon=ba.gettexture('powerupLandMines'), - sound=ba.getsound('ding')) + sound=ba.getsound('ding'), + ) ] # Show special tnt tip on pro preset. @@ -222,7 +249,8 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): 'Take out a group of enemies by\n' 'setting off a bomb near a TNT box.', icon=ba.gettexture('tnt'), - sound=ba.getsound('ding')) + sound=ba.getsound('ding'), + ) ] # Show special curse tip on uber preset. @@ -235,30 +263,37 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): 'Curse boxes turn you into a ticking time bomb.\n' 'The only cure is to quickly grab a health-pack.', icon=ba.gettexture('powerupCurse'), - sound=ba.getsound('ding')) + sound=ba.getsound('ding'), + ) ] self._spawn_info_text = ba.NodeActor( - ba.newnode('text', - attrs={ - 'position': (15, -130), - 'h_attach': 'left', - 'v_attach': 'top', - 'scale': 0.55, - 'color': (0.3, 0.8, 0.3, 1.0), - 'text': '' - })) + ba.newnode( + 'text', + attrs={ + 'position': (15, -130), + 'h_attach': 'left', + 'v_attach': 'top', + 'scale': 0.55, + 'color': (0.3, 0.8, 0.3, 1.0), + 'text': '', + }, + ) + ) ba.setmusic(ba.MusicType.ONSLAUGHT) - self._scoreboard = Scoreboard(label=ba.Lstr(resource='scoreText'), - score_split=0.5) + self._scoreboard = Scoreboard( + label=ba.Lstr(resource='scoreText'), score_split=0.5 + ) def on_begin(self) -> None: super().on_begin() player_count = len(self.players) hard = self._preset not in { - Preset.TRAINING_EASY, Preset.ROOKIE_EASY, Preset.PRO_EASY, - Preset.UBER_EASY + Preset.TRAINING_EASY, + Preset.ROOKIE_EASY, + Preset.PRO_EASY, + Preset.UBER_EASY, } if self._preset in {Preset.TRAINING, Preset.TRAINING_EASY}: ControlsGuide(delay=3.0, lifespan=10.0, bright=True).autoretain() @@ -266,251 +301,352 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): self._have_tnt = False self._excluded_powerups = ['curse', 'land_mines'] self._waves = [ - Wave(base_angle=195, - entries=[ - Spawn(BomberBotLite, spacing=5), - ] * player_count), - Wave(base_angle=130, - entries=[ - Spawn(BrawlerBotLite, spacing=5), - ] * player_count), - Wave(base_angle=195, - entries=[Spawn(BomberBotLite, spacing=10)] * - (player_count + 1)), - Wave(base_angle=130, - entries=[ - Spawn(BrawlerBotLite, spacing=10), - ] * (player_count + 1)), - Wave(base_angle=130, - entries=[ - Spawn(BrawlerBotLite, spacing=5) - if player_count > 1 else None, - Spawn(BrawlerBotLite, spacing=5), - Spacing(30), - Spawn(BomberBotLite, spacing=5) - if player_count > 3 else None, - Spawn(BomberBotLite, spacing=5), - Spacing(30), - Spawn(BrawlerBotLite, spacing=5), - Spawn(BrawlerBotLite, spacing=5) - if player_count > 2 else None, - ]), - Wave(base_angle=195, - entries=[ - Spawn(TriggerBot, spacing=90), - Spawn(TriggerBot, spacing=90) - if player_count > 1 else None, - ]), + Wave( + base_angle=195, + entries=[ + Spawn(BomberBotLite, spacing=5), + ] + * player_count, + ), + Wave( + base_angle=130, + entries=[ + Spawn(BrawlerBotLite, spacing=5), + ] + * player_count, + ), + Wave( + base_angle=195, + entries=[Spawn(BomberBotLite, spacing=10)] + * (player_count + 1), + ), + Wave( + base_angle=130, + entries=[ + Spawn(BrawlerBotLite, spacing=10), + ] + * (player_count + 1), + ), + Wave( + base_angle=130, + entries=[ + Spawn(BrawlerBotLite, spacing=5) + if player_count > 1 + else None, + Spawn(BrawlerBotLite, spacing=5), + Spacing(30), + Spawn(BomberBotLite, spacing=5) + if player_count > 3 + else None, + Spawn(BomberBotLite, spacing=5), + Spacing(30), + Spawn(BrawlerBotLite, spacing=5), + Spawn(BrawlerBotLite, spacing=5) + if player_count > 2 + else None, + ], + ), + Wave( + base_angle=195, + entries=[ + Spawn(TriggerBot, spacing=90), + Spawn(TriggerBot, spacing=90) + if player_count > 1 + else None, + ], + ), ] elif self._preset in {Preset.ROOKIE, Preset.ROOKIE_EASY}: self._have_tnt = False self._excluded_powerups = ['curse'] self._waves = [ - Wave(entries=[ - Spawn(ChargerBot, Point.LEFT_UPPER_MORE - ) if player_count > 2 else None, - Spawn(ChargerBot, Point.LEFT_UPPER), - ]), - Wave(entries=[ - Spawn(BomberBotStaticLite, Point.TURRET_TOP_RIGHT), - Spawn(BrawlerBotLite, Point.RIGHT_UPPER), - Spawn(BrawlerBotLite, Point.RIGHT_LOWER - ) if player_count > 1 else None, - Spawn(BomberBotStaticLite, Point.TURRET_BOTTOM_RIGHT - ) if player_count > 2 else None, - ]), - Wave(entries=[ - Spawn(BomberBotStaticLite, Point.TURRET_BOTTOM_LEFT), - Spawn(TriggerBot, Point.LEFT), - Spawn(TriggerBot, Point.LEFT_LOWER - ) if player_count > 1 else None, - Spawn(TriggerBot, Point.LEFT_UPPER - ) if player_count > 2 else None, - ]), - Wave(entries=[ - Spawn(BrawlerBotLite, Point.TOP_RIGHT), - Spawn(BrawlerBot, Point.TOP_HALF_RIGHT - ) if player_count > 1 else None, - Spawn(BrawlerBotLite, Point.TOP_LEFT), - Spawn(BrawlerBotLite, Point.TOP_HALF_LEFT - ) if player_count > 2 else None, - Spawn(BrawlerBot, Point.TOP), - Spawn(BomberBotStaticLite, Point.TURRET_TOP_MIDDLE), - ]), - Wave(entries=[ - Spawn(TriggerBotStatic, Point.TURRET_BOTTOM_LEFT), - Spawn(TriggerBotStatic, Point.TURRET_BOTTOM_RIGHT), - Spawn(TriggerBot, Point.BOTTOM), - Spawn(TriggerBot, Point.BOTTOM_HALF_RIGHT - ) if player_count > 1 else None, - Spawn(TriggerBot, Point.BOTTOM_HALF_LEFT - ) if player_count > 2 else None, - ]), - Wave(entries=[ - Spawn(BomberBotStaticLite, Point.TURRET_TOP_LEFT), - Spawn(BomberBotStaticLite, Point.TURRET_TOP_RIGHT), - Spawn(ChargerBot, Point.BOTTOM), - Spawn(ChargerBot, Point.BOTTOM_HALF_LEFT - ) if player_count > 1 else None, - Spawn(ChargerBot, Point.BOTTOM_HALF_RIGHT - ) if player_count > 2 else None, - ]), + Wave( + entries=[ + Spawn(ChargerBot, Point.LEFT_UPPER_MORE) + if player_count > 2 + else None, + Spawn(ChargerBot, Point.LEFT_UPPER), + ] + ), + Wave( + entries=[ + Spawn(BomberBotStaticLite, Point.TURRET_TOP_RIGHT), + Spawn(BrawlerBotLite, Point.RIGHT_UPPER), + Spawn(BrawlerBotLite, Point.RIGHT_LOWER) + if player_count > 1 + else None, + Spawn(BomberBotStaticLite, Point.TURRET_BOTTOM_RIGHT) + if player_count > 2 + else None, + ] + ), + Wave( + entries=[ + Spawn(BomberBotStaticLite, Point.TURRET_BOTTOM_LEFT), + Spawn(TriggerBot, Point.LEFT), + Spawn(TriggerBot, Point.LEFT_LOWER) + if player_count > 1 + else None, + Spawn(TriggerBot, Point.LEFT_UPPER) + if player_count > 2 + else None, + ] + ), + Wave( + entries=[ + Spawn(BrawlerBotLite, Point.TOP_RIGHT), + Spawn(BrawlerBot, Point.TOP_HALF_RIGHT) + if player_count > 1 + else None, + Spawn(BrawlerBotLite, Point.TOP_LEFT), + Spawn(BrawlerBotLite, Point.TOP_HALF_LEFT) + if player_count > 2 + else None, + Spawn(BrawlerBot, Point.TOP), + Spawn(BomberBotStaticLite, Point.TURRET_TOP_MIDDLE), + ] + ), + Wave( + entries=[ + Spawn(TriggerBotStatic, Point.TURRET_BOTTOM_LEFT), + Spawn(TriggerBotStatic, Point.TURRET_BOTTOM_RIGHT), + Spawn(TriggerBot, Point.BOTTOM), + Spawn(TriggerBot, Point.BOTTOM_HALF_RIGHT) + if player_count > 1 + else None, + Spawn(TriggerBot, Point.BOTTOM_HALF_LEFT) + if player_count > 2 + else None, + ] + ), + Wave( + entries=[ + Spawn(BomberBotStaticLite, Point.TURRET_TOP_LEFT), + Spawn(BomberBotStaticLite, Point.TURRET_TOP_RIGHT), + Spawn(ChargerBot, Point.BOTTOM), + Spawn(ChargerBot, Point.BOTTOM_HALF_LEFT) + if player_count > 1 + else None, + Spawn(ChargerBot, Point.BOTTOM_HALF_RIGHT) + if player_count > 2 + else None, + ] + ), ] elif self._preset in {Preset.PRO, Preset.PRO_EASY}: self._excluded_powerups = ['curse'] self._have_tnt = True self._waves = [ - Wave(base_angle=-50, - entries=[ - Spawn(BrawlerBot, spacing=12) - if player_count > 3 else None, - Spawn(BrawlerBot, spacing=12), - Spawn(BomberBot, spacing=6), - Spawn(BomberBot, spacing=6) - if self._preset is Preset.PRO else None, - Spawn(BomberBot, spacing=6) - if player_count > 1 else None, - Spawn(BrawlerBot, spacing=12), - Spawn(BrawlerBot, spacing=12) - if player_count > 2 else None, - ]), - Wave(base_angle=180, - entries=[ - Spawn(BrawlerBot, spacing=6) - if player_count > 3 else None, - Spawn(BrawlerBot, spacing=6) - if self._preset is Preset.PRO else None, - Spawn(BrawlerBot, spacing=6), - Spawn(ChargerBot, spacing=45), - Spawn(ChargerBot, spacing=45) - if player_count > 1 else None, - Spawn(BrawlerBot, spacing=6), - Spawn(BrawlerBot, spacing=6) - if self._preset is Preset.PRO else None, - Spawn(BrawlerBot, spacing=6) - if player_count > 2 else None, - ]), - Wave(base_angle=0, - entries=[ - Spawn(ChargerBot, spacing=30), - Spawn(TriggerBot, spacing=30), - Spawn(TriggerBot, spacing=30), - Spawn(TriggerBot, spacing=30) - if self._preset is Preset.PRO else None, - Spawn(TriggerBot, spacing=30) - if player_count > 1 else None, - Spawn(TriggerBot, spacing=30) - if player_count > 3 else None, - Spawn(ChargerBot, spacing=30), - ]), - Wave(base_angle=90, - entries=[ - Spawn(StickyBot, spacing=50), - Spawn(StickyBot, spacing=50) - if self._preset is Preset.PRO else None, - Spawn(StickyBot, spacing=50), - Spawn(StickyBot, spacing=50) - if player_count > 1 else None, - Spawn(StickyBot, spacing=50) - if player_count > 3 else None, - ]), - Wave(base_angle=0, - entries=[ - Spawn(TriggerBot, spacing=72), - Spawn(TriggerBot, spacing=72), - Spawn(TriggerBot, spacing=72) - if self._preset is Preset.PRO else None, - Spawn(TriggerBot, spacing=72), - Spawn(TriggerBot, spacing=72), - Spawn(TriggerBot, spacing=36) - if player_count > 2 else None, - ]), - Wave(base_angle=30, - entries=[ - Spawn(ChargerBotProShielded, spacing=50), - Spawn(ChargerBotProShielded, spacing=50), - Spawn(ChargerBotProShielded, spacing=50) - if self._preset is Preset.PRO else None, - Spawn(ChargerBotProShielded, spacing=50) - if player_count > 1 else None, - Spawn(ChargerBotProShielded, spacing=50) - if player_count > 2 else None, - ]) + Wave( + base_angle=-50, + entries=[ + Spawn(BrawlerBot, spacing=12) + if player_count > 3 + else None, + Spawn(BrawlerBot, spacing=12), + Spawn(BomberBot, spacing=6), + Spawn(BomberBot, spacing=6) + if self._preset is Preset.PRO + else None, + Spawn(BomberBot, spacing=6) + if player_count > 1 + else None, + Spawn(BrawlerBot, spacing=12), + Spawn(BrawlerBot, spacing=12) + if player_count > 2 + else None, + ], + ), + Wave( + base_angle=180, + entries=[ + Spawn(BrawlerBot, spacing=6) + if player_count > 3 + else None, + Spawn(BrawlerBot, spacing=6) + if self._preset is Preset.PRO + else None, + Spawn(BrawlerBot, spacing=6), + Spawn(ChargerBot, spacing=45), + Spawn(ChargerBot, spacing=45) + if player_count > 1 + else None, + Spawn(BrawlerBot, spacing=6), + Spawn(BrawlerBot, spacing=6) + if self._preset is Preset.PRO + else None, + Spawn(BrawlerBot, spacing=6) + if player_count > 2 + else None, + ], + ), + Wave( + base_angle=0, + entries=[ + Spawn(ChargerBot, spacing=30), + Spawn(TriggerBot, spacing=30), + Spawn(TriggerBot, spacing=30), + Spawn(TriggerBot, spacing=30) + if self._preset is Preset.PRO + else None, + Spawn(TriggerBot, spacing=30) + if player_count > 1 + else None, + Spawn(TriggerBot, spacing=30) + if player_count > 3 + else None, + Spawn(ChargerBot, spacing=30), + ], + ), + Wave( + base_angle=90, + entries=[ + Spawn(StickyBot, spacing=50), + Spawn(StickyBot, spacing=50) + if self._preset is Preset.PRO + else None, + Spawn(StickyBot, spacing=50), + Spawn(StickyBot, spacing=50) + if player_count > 1 + else None, + Spawn(StickyBot, spacing=50) + if player_count > 3 + else None, + ], + ), + Wave( + base_angle=0, + entries=[ + Spawn(TriggerBot, spacing=72), + Spawn(TriggerBot, spacing=72), + Spawn(TriggerBot, spacing=72) + if self._preset is Preset.PRO + else None, + Spawn(TriggerBot, spacing=72), + Spawn(TriggerBot, spacing=72), + Spawn(TriggerBot, spacing=36) + if player_count > 2 + else None, + ], + ), + Wave( + base_angle=30, + entries=[ + Spawn(ChargerBotProShielded, spacing=50), + Spawn(ChargerBotProShielded, spacing=50), + Spawn(ChargerBotProShielded, spacing=50) + if self._preset is Preset.PRO + else None, + Spawn(ChargerBotProShielded, spacing=50) + if player_count > 1 + else None, + Spawn(ChargerBotProShielded, spacing=50) + if player_count > 2 + else None, + ], + ), ] elif self._preset in {Preset.UBER, Preset.UBER_EASY}: # Show controls help in demo/arcade modes. if ba.app.demo_mode or ba.app.arcade_mode: - ControlsGuide(delay=3.0, lifespan=10.0, - bright=True).autoretain() + ControlsGuide( + delay=3.0, lifespan=10.0, bright=True + ).autoretain() self._have_tnt = True self._excluded_powerups = [] self._waves = [ - Wave(entries=[ - Spawn(BomberBotProStatic, Point.TURRET_TOP_MIDDLE_LEFT - ) if hard else None, - Spawn(BomberBotProStatic, Point.TURRET_TOP_MIDDLE_RIGHT), - Spawn(BomberBotProStatic, Point.TURRET_TOP_LEFT - ) if player_count > 2 else None, - Spawn(ExplodeyBot, Point.TOP_RIGHT), - Delay(4.0), - Spawn(ExplodeyBot, Point.TOP_LEFT), - ]), - Wave(entries=[ - Spawn(ChargerBot, Point.LEFT), - Spawn(ChargerBot, Point.RIGHT), - Spawn(ChargerBot, Point.RIGHT_UPPER_MORE - ) if player_count > 2 else None, - Spawn(BomberBotProStatic, Point.TURRET_TOP_LEFT), - Spawn(BomberBotProStatic, Point.TURRET_TOP_RIGHT), - ]), - Wave(entries=[ - Spawn(TriggerBotPro, Point.TOP_RIGHT), - Spawn(TriggerBotPro, Point.RIGHT_UPPER_MORE - ) if player_count > 1 else None, - Spawn(TriggerBotPro, Point.RIGHT_UPPER), - Spawn(TriggerBotPro, Point.RIGHT_LOWER) if hard else None, - Spawn(TriggerBotPro, Point.RIGHT_LOWER_MORE - ) if player_count > 2 else None, - Spawn(TriggerBotPro, Point.BOTTOM_RIGHT), - ]), - Wave(entries=[ - Spawn(ChargerBotProShielded, Point.BOTTOM_RIGHT), - Spawn(ChargerBotProShielded, Point.BOTTOM - ) if player_count > 2 else None, - Spawn(ChargerBotProShielded, Point.BOTTOM_LEFT), - Spawn(ChargerBotProShielded, Point.TOP) if hard else None, - Spawn(BomberBotProStatic, Point.TURRET_TOP_MIDDLE), - ]), - Wave(entries=[ - Spawn(ExplodeyBot, Point.LEFT_UPPER), - Delay(1.0), - Spawn(BrawlerBotProShielded, Point.LEFT_LOWER), - Spawn(BrawlerBotProShielded, Point.LEFT_LOWER_MORE), - Delay(4.0), - Spawn(ExplodeyBot, Point.RIGHT_UPPER), - Delay(1.0), - Spawn(BrawlerBotProShielded, Point.RIGHT_LOWER), - Spawn(BrawlerBotProShielded, Point.RIGHT_UPPER_MORE), - Delay(4.0), - Spawn(ExplodeyBot, Point.LEFT), - Delay(5.0), - Spawn(ExplodeyBot, Point.RIGHT), - ]), - Wave(entries=[ - Spawn(BomberBotProStatic, Point.TURRET_TOP_LEFT), - Spawn(BomberBotProStatic, Point.TURRET_TOP_RIGHT), - Spawn(BomberBotProStatic, Point.TURRET_BOTTOM_LEFT), - Spawn(BomberBotProStatic, Point.TURRET_BOTTOM_RIGHT), - Spawn(BomberBotProStatic, Point.TURRET_TOP_MIDDLE_LEFT - ) if hard else None, - Spawn(BomberBotProStatic, Point.TURRET_TOP_MIDDLE_RIGHT - ) if hard else None, - ]) + Wave( + entries=[ + Spawn(BomberBotProStatic, Point.TURRET_TOP_MIDDLE_LEFT) + if hard + else None, + Spawn( + BomberBotProStatic, Point.TURRET_TOP_MIDDLE_RIGHT + ), + Spawn(BomberBotProStatic, Point.TURRET_TOP_LEFT) + if player_count > 2 + else None, + Spawn(ExplodeyBot, Point.TOP_RIGHT), + Delay(4.0), + Spawn(ExplodeyBot, Point.TOP_LEFT), + ] + ), + Wave( + entries=[ + Spawn(ChargerBot, Point.LEFT), + Spawn(ChargerBot, Point.RIGHT), + Spawn(ChargerBot, Point.RIGHT_UPPER_MORE) + if player_count > 2 + else None, + Spawn(BomberBotProStatic, Point.TURRET_TOP_LEFT), + Spawn(BomberBotProStatic, Point.TURRET_TOP_RIGHT), + ] + ), + Wave( + entries=[ + Spawn(TriggerBotPro, Point.TOP_RIGHT), + Spawn(TriggerBotPro, Point.RIGHT_UPPER_MORE) + if player_count > 1 + else None, + Spawn(TriggerBotPro, Point.RIGHT_UPPER), + Spawn(TriggerBotPro, Point.RIGHT_LOWER) + if hard + else None, + Spawn(TriggerBotPro, Point.RIGHT_LOWER_MORE) + if player_count > 2 + else None, + Spawn(TriggerBotPro, Point.BOTTOM_RIGHT), + ] + ), + Wave( + entries=[ + Spawn(ChargerBotProShielded, Point.BOTTOM_RIGHT), + Spawn(ChargerBotProShielded, Point.BOTTOM) + if player_count > 2 + else None, + Spawn(ChargerBotProShielded, Point.BOTTOM_LEFT), + Spawn(ChargerBotProShielded, Point.TOP) + if hard + else None, + Spawn(BomberBotProStatic, Point.TURRET_TOP_MIDDLE), + ] + ), + Wave( + entries=[ + Spawn(ExplodeyBot, Point.LEFT_UPPER), + Delay(1.0), + Spawn(BrawlerBotProShielded, Point.LEFT_LOWER), + Spawn(BrawlerBotProShielded, Point.LEFT_LOWER_MORE), + Delay(4.0), + Spawn(ExplodeyBot, Point.RIGHT_UPPER), + Delay(1.0), + Spawn(BrawlerBotProShielded, Point.RIGHT_LOWER), + Spawn(BrawlerBotProShielded, Point.RIGHT_UPPER_MORE), + Delay(4.0), + Spawn(ExplodeyBot, Point.LEFT), + Delay(5.0), + Spawn(ExplodeyBot, Point.RIGHT), + ] + ), + Wave( + entries=[ + Spawn(BomberBotProStatic, Point.TURRET_TOP_LEFT), + Spawn(BomberBotProStatic, Point.TURRET_TOP_RIGHT), + Spawn(BomberBotProStatic, Point.TURRET_BOTTOM_LEFT), + Spawn(BomberBotProStatic, Point.TURRET_BOTTOM_RIGHT), + Spawn(BomberBotProStatic, Point.TURRET_TOP_MIDDLE_LEFT) + if hard + else None, + Spawn(BomberBotProStatic, Point.TURRET_TOP_MIDDLE_RIGHT) + if hard + else None, + ] + ), ] # We generate these on the fly in endless. @@ -525,11 +661,16 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): # FIXME: Should migrate to use setup_standard_powerup_drops(). # Spit out a few powerups and start dropping more shortly. - self._drop_powerups(standard_points=True, - poweruptype='curse' if self._preset - in [Preset.UBER, Preset.UBER_EASY] else - ('land_mines' if self._preset - in [Preset.ROOKIE, Preset.ROOKIE_EASY] else None)) + self._drop_powerups( + standard_points=True, + poweruptype='curse' + if self._preset in [Preset.UBER, Preset.UBER_EASY] + else ( + 'land_mines' + if self._preset in [Preset.ROOKIE, Preset.ROOKIE_EASY] + else None + ), + ) ba.timer(4.0, self._start_powerup_drops) # Our TNT spawner (if applicable). @@ -554,9 +695,14 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): totaldudes += dudes return totalpts, totaldudes - def _get_distribution(self, target_points: int, min_dudes: int, - max_dudes: int, group_count: int, - max_level: int) -> list[list[tuple[int, int]]]: + def _get_distribution( + self, + target_points: int, + min_dudes: int, + max_dudes: int, + group_count: int, + max_level: int, + ) -> list[list[tuple[int, int]]]: """Calculate a distribution of bad guys given some params.""" max_iterations = 10 + max_dudes * 2 @@ -571,11 +717,12 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): if max_level > 3: types.append(4) for iteration in range(max_iterations): - diff = self._add_dist_entry_if_possible(groups, max_dudes, - target_points, types) + diff = self._add_dist_entry_if_possible( + groups, max_dudes, target_points, types + ) total_points, total_dudes = self._get_dist_grp_totals(groups) - full = (total_points >= target_points) + full = total_points >= target_points if full: # Every so often, delete a random entry just to @@ -585,14 +732,16 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): # If we don't have enough dudes, kill the group with # the biggest point value. - elif (total_dudes < min_dudes - and iteration != max_iterations - 1): + elif ( + total_dudes < min_dudes and iteration != max_iterations - 1 + ): self._delete_biggest_dist_entry(groups) # If we've got too many dudes, kill the group with the # smallest point value. - elif (total_dudes > max_dudes - and iteration != max_iterations - 1): + elif ( + total_dudes > max_dudes and iteration != max_iterations - 1 + ): self._delete_smallest_dist_entry(groups) # Close enough.. we're done. @@ -602,9 +751,13 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): return groups - def _add_dist_entry_if_possible(self, groups: list[list[tuple[int, int]]], - max_dudes: int, target_points: int, - types: list[int]) -> int: + def _add_dist_entry_if_possible( + self, + groups: list[list[tuple[int, int]]], + max_dudes: int, + target_points: int, + types: list[int], + ) -> int: # See how much we're off our target by. total_points, total_dudes = self._get_dist_grp_totals(groups) diff = target_points - total_points @@ -627,7 +780,8 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): return diff def _delete_smallest_dist_entry( - self, groups: list[list[tuple[int, int]]]) -> None: + self, groups: list[list[tuple[int, int]]] + ) -> None: smallest_value = 9999 smallest_entry = None smallest_entry_group = None @@ -642,7 +796,8 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): smallest_entry_group.remove(smallest_entry) def _delete_biggest_dist_entry( - self, groups: list[list[tuple[int, int]]]) -> None: + self, groups: list[list[tuple[int, int]]] + ) -> None: biggest_value = 9999 biggest_entry = None biggest_entry_group = None @@ -656,8 +811,9 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): assert biggest_entry_group is not None biggest_entry_group.remove(biggest_entry) - def _delete_random_dist_entry(self, - groups: list[list[tuple[int, int]]]) -> None: + def _delete_random_dist_entry( + self, groups: list[list[tuple[int, int]]] + ) -> None: entry_count = 0 for group in groups: for _ in group: @@ -676,59 +832,76 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): # We keep track of who got hurt each wave for score purposes. player.has_been_hurt = False - pos = (self._spawn_center[0] + random.uniform(-1.5, 1.5), - self._spawn_center[1], - self._spawn_center[2] + random.uniform(-1.5, 1.5)) + pos = ( + self._spawn_center[0] + random.uniform(-1.5, 1.5), + self._spawn_center[1], + self._spawn_center[2] + random.uniform(-1.5, 1.5), + ) spaz = self.spawn_player_spaz(player, position=pos) if self._preset in { - Preset.TRAINING_EASY, Preset.ROOKIE_EASY, Preset.PRO_EASY, - Preset.UBER_EASY + Preset.TRAINING_EASY, + Preset.ROOKIE_EASY, + Preset.PRO_EASY, + Preset.UBER_EASY, }: spaz.impact_scale = 0.25 spaz.add_dropped_bomb_callback(self._handle_player_dropped_bomb) return spaz - def _handle_player_dropped_bomb(self, player: ba.Actor, - bomb: ba.Actor) -> None: + def _handle_player_dropped_bomb( + self, player: ba.Actor, bomb: ba.Actor + ) -> None: del player, bomb # Unused. self._player_has_dropped_bomb = True - def _drop_powerup(self, - index: int, - poweruptype: str | None = None) -> None: - poweruptype = (PowerupBoxFactory.get().get_random_powerup_type( - forcetype=poweruptype, excludetypes=self._excluded_powerups)) - PowerupBox(position=self.map.powerup_spawn_points[index], - poweruptype=poweruptype).autoretain() + def _drop_powerup(self, index: int, poweruptype: str | None = None) -> None: + poweruptype = PowerupBoxFactory.get().get_random_powerup_type( + forcetype=poweruptype, excludetypes=self._excluded_powerups + ) + PowerupBox( + position=self.map.powerup_spawn_points[index], + poweruptype=poweruptype, + ).autoretain() def _start_powerup_drops(self) -> None: - self._powerup_drop_timer = ba.Timer(3.0, - ba.WeakCall(self._drop_powerups), - repeat=True) + self._powerup_drop_timer = ba.Timer( + 3.0, ba.WeakCall(self._drop_powerups), repeat=True + ) - def _drop_powerups(self, - standard_points: bool = False, - poweruptype: str | None = None) -> None: + def _drop_powerups( + self, standard_points: bool = False, poweruptype: str | None = None + ) -> None: """Generic powerup drop.""" if standard_points: points = self.map.powerup_spawn_points for i in range(len(points)): ba.timer( 1.0 + i * 0.5, - ba.WeakCall(self._drop_powerup, i, - poweruptype if i == 0 else None)) + ba.WeakCall( + self._drop_powerup, i, poweruptype if i == 0 else None + ), + ) else: - point = (self._powerup_center[0] + random.uniform( - -1.0 * self._powerup_spread[0], 1.0 * self._powerup_spread[0]), - self._powerup_center[1], - self._powerup_center[2] + random.uniform( - -self._powerup_spread[1], self._powerup_spread[1])) + point = ( + self._powerup_center[0] + + random.uniform( + -1.0 * self._powerup_spread[0], + 1.0 * self._powerup_spread[0], + ), + self._powerup_center[1], + self._powerup_center[2] + + random.uniform( + -self._powerup_spread[1], self._powerup_spread[1] + ), + ) # Drop one random one somewhere. PowerupBox( position=point, poweruptype=PowerupBoxFactory.get().get_random_powerup_type( - excludetypes=self._excluded_powerups)).autoretain() + excludetypes=self._excluded_powerups + ), + ).autoretain() def do_end(self, outcome: str, delay: float = 0.0) -> None: """End the game with the specified outcome.""" @@ -746,9 +919,10 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): 'outcome': outcome, 'score': score, 'fail_message': fail_message, - 'playerinfos': self.initialplayerinfos + 'playerinfos': self.initialplayerinfos, }, - delay=delay) + delay=delay, + ) def _award_completion_achievements(self) -> None: if self._preset in {Preset.TRAINING, Preset.TRAINING_EASY}: @@ -770,23 +944,28 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): # If we have no living bots, go to the next wave. assert self._bots is not None - if (self._can_end_wave and not self._bots.have_living_bots() - and not self._game_over): + if ( + self._can_end_wave + and not self._bots.have_living_bots() + and not self._game_over + ): self._can_end_wave = False self._time_bonus_timer = None self._time_bonus_text = None if self._preset in {Preset.ENDLESS, Preset.ENDLESS_TOURNAMENT}: won = False else: - won = (self._wavenum == len(self._waves)) + won = self._wavenum == len(self._waves) base_delay = 4.0 if won else 0.0 # Reward time bonus. if self._time_bonus > 0: ba.timer(0, lambda: ba.playsound(self._cashregistersound)) - ba.timer(base_delay, - ba.WeakCall(self._award_time_bonus, self._time_bonus)) + ba.timer( + base_delay, + ba.WeakCall(self._award_time_bonus, self._time_bonus), + ) base_delay += 1.0 # Reward flawless bonus. @@ -797,15 +976,16 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): have_flawless = True ba.timer( base_delay, - ba.WeakCall(self._award_flawless_bonus, player)) + ba.WeakCall(self._award_flawless_bonus, player), + ) player.has_been_hurt = False # reset if have_flawless: base_delay += 1.0 if won: - self.show_zoom_message(ba.Lstr(resource='victoryText'), - scale=1.0, - duration=4.0) + self.show_zoom_message( + ba.Lstr(resource='victoryText'), scale=1.0, duration=4.0 + ) self.celebrate(20.0) self._award_completion_achievements() ba.timer(base_delay, ba.WeakCall(self._award_completion_bonus)) @@ -840,18 +1020,25 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): scale=1.4, color=(0.6, 0.6, 1.0, 1.0), title=ba.Lstr(resource='completionBonusText'), - screenmessage=False) + screenmessage=False, + ) except Exception: ba.print_exception() def _award_time_bonus(self, bonus: int) -> None: ba.playsound(self._cashregistersound) - PopupText(ba.Lstr(value='+${A} ${B}', - subs=[('${A}', str(bonus)), - ('${B}', ba.Lstr(resource='timeBonusText'))]), - color=(1, 1, 0.5, 1), - scale=1.0, - position=(0, 3, -1)).autoretain() + PopupText( + ba.Lstr( + value='+${A} ${B}', + subs=[ + ('${A}', str(bonus)), + ('${B}', ba.Lstr(resource='timeBonusText')), + ], + ), + color=(1, 1, 0.5, 1), + scale=1.0, + position=(0, 3, -1), + ).autoretain() self._score += self._time_bonus self._update_scores() @@ -866,14 +1053,15 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): scale=1.2, color=(0.6, 1.0, 0.6, 1.0), title=ba.Lstr(resource='flawlessWaveText'), - screenmessage=False) + screenmessage=False, + ) except Exception: ba.print_exception() def _start_time_bonus_timer(self) -> None: - self._time_bonus_timer = ba.Timer(1.0, - ba.WeakCall(self._update_time_bonus), - repeat=True) + self._time_bonus_timer = ba.Timer( + 1.0, ba.WeakCall(self._update_time_bonus), repeat=True + ) def _update_player_spawn_info(self) -> None: @@ -885,27 +1073,34 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): else: text: str | ba.Lstr = '' for player in self.players: - if (not player.is_alive() - and (self._preset - in [Preset.ENDLESS, Preset.ENDLESS_TOURNAMENT] or - (player.respawn_wave <= len(self._waves)))): - rtxt = ba.Lstr(resource='onslaughtRespawnText', - subs=[('${PLAYER}', player.getname()), - ('${WAVE}', str(player.respawn_wave)) - ]) - text = ba.Lstr(value='${A}${B}\n', - subs=[ - ('${A}', text), - ('${B}', rtxt), - ]) + if not player.is_alive() and ( + self._preset in [Preset.ENDLESS, Preset.ENDLESS_TOURNAMENT] + or (player.respawn_wave <= len(self._waves)) + ): + rtxt = ba.Lstr( + resource='onslaughtRespawnText', + subs=[ + ('${PLAYER}', player.getname()), + ('${WAVE}', str(player.respawn_wave)), + ], + ) + text = ba.Lstr( + value='${A}${B}\n', + subs=[ + ('${A}', text), + ('${B}', rtxt), + ], + ) self._spawn_info_text.node.text = text def _respawn_players_for_wave(self) -> None: # Respawn applicable players. if self._wavenum > 1 and not self.is_waiting_for_continue(): for player in self.players: - if (not player.is_alive() - and player.respawn_wave == self._wavenum): + if ( + not player.is_alive() + and player.respawn_wave == self._wavenum + ): self.spawn_player(player) self._update_player_spawn_info() @@ -940,23 +1135,27 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): point = info.point if point is not None: assert bot_type_2 is not None - spcall = ba.WeakCall(self.add_bot_at_point, point, bot_type_2, - spawn_time) + spcall = ba.WeakCall( + self.add_bot_at_point, point, bot_type_2, spawn_time + ) ba.timer(tval, spcall) tval += dtime else: spacing = info.spacing bot_angle += spacing * 0.5 if bot_type_2 is not None: - tcall = ba.WeakCall(self.add_bot_at_angle, bot_angle, - bot_type_2, spawn_time) + tcall = ba.WeakCall( + self.add_bot_at_angle, bot_angle, bot_type_2, spawn_time + ) ba.timer(tval, tcall) tval += dtime bot_angle += spacing * 0.5 # We can end the wave after all the spawning happens. - ba.timer(tval + spawn_time - dtime + 0.01, - ba.WeakCall(self._set_can_end_wave)) + ba.timer( + tval + spawn_time - dtime + 0.01, + ba.WeakCall(self._set_can_end_wave), + ) def _start_next_wave(self) -> None: @@ -976,67 +1175,98 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): def _update_wave_ui_and_bonuses(self) -> None: - self.show_zoom_message(ba.Lstr(value='${A} ${B}', - subs=[('${A}', - ba.Lstr(resource='waveText')), - ('${B}', str(self._wavenum))]), - scale=1.0, - duration=1.0, - trail=True) + self.show_zoom_message( + ba.Lstr( + value='${A} ${B}', + subs=[ + ('${A}', ba.Lstr(resource='waveText')), + ('${B}', str(self._wavenum)), + ], + ), + scale=1.0, + duration=1.0, + trail=True, + ) # Reset our time bonus. tbtcolor = (1, 1, 0, 1) - tbttxt = ba.Lstr(value='${A}: ${B}', - subs=[ - ('${A}', ba.Lstr(resource='timeBonusText')), - ('${B}', str(self._time_bonus)), - ]) + tbttxt = ba.Lstr( + value='${A}: ${B}', + subs=[ + ('${A}', ba.Lstr(resource='timeBonusText')), + ('${B}', str(self._time_bonus)), + ], + ) self._time_bonus_text = ba.NodeActor( - ba.newnode('text', - attrs={ - 'v_attach': 'top', - 'h_attach': 'center', - 'h_align': 'center', - 'vr_depth': -30, - 'color': tbtcolor, - 'shadow': 1.0, - 'flatness': 1.0, - 'position': (0, -60), - 'scale': 0.8, - 'text': tbttxt - })) + ba.newnode( + 'text', + attrs={ + 'v_attach': 'top', + 'h_attach': 'center', + 'h_align': 'center', + 'vr_depth': -30, + 'color': tbtcolor, + 'shadow': 1.0, + 'flatness': 1.0, + 'position': (0, -60), + 'scale': 0.8, + 'text': tbttxt, + }, + ) + ) ba.timer(5.0, ba.WeakCall(self._start_time_bonus_timer)) wtcolor = (1, 1, 1, 1) wttxt = ba.Lstr( value='${A} ${B}', - subs=[('${A}', ba.Lstr(resource='waveText')), - ('${B}', str(self._wavenum) + - ('' if self._preset - in [Preset.ENDLESS, Preset.ENDLESS_TOURNAMENT] else - ('/' + str(len(self._waves)))))]) + subs=[ + ('${A}', ba.Lstr(resource='waveText')), + ( + '${B}', + str(self._wavenum) + + ( + '' + if self._preset + in [Preset.ENDLESS, Preset.ENDLESS_TOURNAMENT] + else ('/' + str(len(self._waves))) + ), + ), + ], + ) self._wave_text = ba.NodeActor( - ba.newnode('text', - attrs={ - 'v_attach': 'top', - 'h_attach': 'center', - 'h_align': 'center', - 'vr_depth': -10, - 'color': wtcolor, - 'shadow': 1.0, - 'flatness': 1.0, - 'position': (0, -40), - 'scale': 1.3, - 'text': wttxt - })) + ba.newnode( + 'text', + attrs={ + 'v_attach': 'top', + 'h_attach': 'center', + 'h_align': 'center', + 'vr_depth': -10, + 'color': wtcolor, + 'shadow': 1.0, + 'flatness': 1.0, + 'position': (0, -40), + 'scale': 1.3, + 'text': wttxt, + }, + ) + ) def _bot_levels_for_wave(self) -> list[list[type[SpazBot]]]: level = self._wavenum bot_types = [ - BomberBot, BrawlerBot, TriggerBot, ChargerBot, BomberBotPro, - BrawlerBotPro, TriggerBotPro, BomberBotProShielded, ExplodeyBot, - ChargerBotProShielded, StickyBot, BrawlerBotProShielded, - TriggerBotProShielded + BomberBot, + BrawlerBot, + TriggerBot, + ChargerBot, + BomberBotPro, + BrawlerBotPro, + TriggerBotPro, + BomberBotProShielded, + ExplodeyBot, + ChargerBotProShielded, + StickyBot, + BrawlerBotProShielded, + TriggerBotProShielded, ] if level > 5: bot_types += [ @@ -1054,18 +1284,24 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): ] if level > 10: bot_types += [ - TriggerBotProShielded, TriggerBotProShielded, - TriggerBotProShielded, TriggerBotProShielded + TriggerBotProShielded, + TriggerBotProShielded, + TriggerBotProShielded, + TriggerBotProShielded, ] if level > 13: bot_types += [ - TriggerBotProShielded, TriggerBotProShielded, - TriggerBotProShielded, TriggerBotProShielded + TriggerBotProShielded, + TriggerBotProShielded, + TriggerBotProShielded, + TriggerBotProShielded, ] - bot_levels = [[b for b in bot_types if b.points_mult == 1], - [b for b in bot_types if b.points_mult == 2], - [b for b in bot_types if b.points_mult == 3], - [b for b in bot_types if b.points_mult == 4]] + bot_levels = [ + [b for b in bot_types if b.points_mult == 1], + [b for b in bot_types if b.points_mult == 2], + [b for b in bot_types if b.points_mult == 3], + [b for b in bot_types if b.points_mult == 4], + ] # Make sure all lists have something in them if not all(bot_levels): @@ -1073,9 +1309,11 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): return bot_levels def _add_entries_for_distribution_group( - self, group: list[tuple[int, int]], - bot_levels: list[list[type[SpazBot]]], - all_entries: list[Spawn | Spacing | Delay | None]) -> None: + self, + group: list[tuple[int, int]], + bot_levels: list[list[type[SpazBot]]], + all_entries: list[Spawn | Spacing | Delay | None], + ) -> None: entries: list[Spawn | Spacing | Delay | None] = [] for entry in group: bot_level = bot_levels[entry[0] - 1] @@ -1095,8 +1333,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): entries.append(Spawn(bot_type, spacing=spacing)) if entries: all_entries += entries - all_entries.append( - Spacing(40.0 if random.random() < 0.5 else 80.0)) + all_entries.append(Spacing(40.0 if random.random() < 0.5 else 80.0)) def _generate_random_wave(self) -> Wave: level = self._wavenum @@ -1105,16 +1342,18 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): target_points = level * 3 - 2 min_dudes = min(1 + level // 3, 10) max_dudes = min(10, level + 1) - max_level = 4 if level > 6 else (3 if level > 3 else - (2 if level > 2 else 1)) + max_level = ( + 4 if level > 6 else (3 if level > 3 else (2 if level > 2 else 1)) + ) group_count = 3 - distribution = self._get_distribution(target_points, min_dudes, - max_dudes, group_count, - max_level) + distribution = self._get_distribution( + target_points, min_dudes, max_dudes, group_count, max_level + ) all_entries: list[Spawn | Spacing | Delay | None] = [] for group in distribution: - self._add_entries_for_distribution_group(group, bot_levels, - all_entries) + self._add_entries_for_distribution_group( + group, bot_levels, all_entries + ) angle_rand = random.random() if angle_rand > 0.75: base_angle = 130.0 @@ -1128,10 +1367,9 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): wave = Wave(base_angle=base_angle, entries=all_entries) return wave - def add_bot_at_point(self, - point: Point, - spaz_type: type[SpazBot], - spawn_time: float = 1.0) -> None: + def add_bot_at_point( + self, point: Point, spaz_type: type[SpazBot], spawn_time: float = 1.0 + ) -> None: """Add a new bot at a specified named point.""" if self._game_over: return @@ -1140,10 +1378,9 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): assert self._bots is not None self._bots.spawn_bot(spaz_type, pos=pointpos, spawn_time=spawn_time) - def add_bot_at_angle(self, - angle: float, - spaz_type: type[SpazBot], - spawn_time: float = 1.0) -> None: + def add_bot_at_angle( + self, angle: float, spaz_type: type[SpazBot], spawn_time: float = 1.0 + ) -> None: """Add a new bot at a specified angle (for circular maps).""" if self._game_over: return @@ -1160,15 +1397,18 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): assert self._time_bonus_text.node self._time_bonus_text.node.text = ba.Lstr( value='${A}: ${B}', - subs=[('${A}', ba.Lstr(resource='timeBonusText')), - ('${B}', str(self._time_bonus))]) + subs=[ + ('${A}', ba.Lstr(resource='timeBonusText')), + ('${B}', str(self._time_bonus)), + ], + ) else: self._time_bonus_text = None def _start_updating_waves(self) -> None: - self._wave_update_timer = ba.Timer(2.0, - ba.WeakCall(self._update_waves), - repeat=True) + self._wave_update_timer = ba.Timer( + 2.0, ba.WeakCall(self._update_waves), repeat=True + ) def _update_scores(self) -> None: score = self._score @@ -1218,15 +1458,18 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): target = None killerplayer = msg.killerplayer - self.stats.player_scored(killerplayer, - pts, - target=target, - kill=True, - screenmessage=False, - importance=importance) - ba.playsound(self._dingsound - if importance == 1 else self._dingsoundhigh, - volume=0.6) + self.stats.player_scored( + killerplayer, + pts, + target=target, + kill=True, + screenmessage=False, + importance=importance, + ) + ba.playsound( + self._dingsound if importance == 1 else self._dingsoundhigh, + volume=0.6, + ) # Normally we pull scores from the score-set, but if there's # no player lets be explicit. @@ -1258,8 +1501,9 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): if msg.spazbot.last_attacked_type == ('explosion', 'tnt'): self._tnt_kills += 1 if self._tnt_kills >= 6: - ba.timer(0.5, ba.WeakCall(self._award_achievement, - 'TNT Terror')) + ba.timer( + 0.5, ba.WeakCall(self._award_achievement, 'TNT Terror') + ) def _handle_pro_kill_achievements(self, msg: SpazBotDiedMessage) -> None: @@ -1269,19 +1513,21 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): if self._tnt_kills >= 3: ba.timer( 0.5, - ba.WeakCall(self._award_achievement, - 'Boom Goes the Dynamite')) + ba.WeakCall( + self._award_achievement, 'Boom Goes the Dynamite' + ), + ) - def _handle_rookie_kill_achievements(self, - msg: SpazBotDiedMessage) -> None: + def _handle_rookie_kill_achievements(self, msg: SpazBotDiedMessage) -> None: # Land-mine achievement: if msg.spazbot.last_attacked_type == ('explosion', 'land_mine'): self._land_mine_kills += 1 if self._land_mine_kills >= 3: self._award_achievement('Mine Games') - def _handle_training_kill_achievements(self, - msg: SpazBotDiedMessage) -> None: + def _handle_training_kill_achievements( + self, msg: SpazBotDiedMessage + ) -> None: # Toss-off-map achievement: if msg.spazbot.last_attacked_type == ('picked_up', 'default'): self._throw_off_kills += 1 diff --git a/dist/ba_data/python/bastd/game/race.py b/dist/ba_data/python/bastd/game/race.py index e21fbe5..cbf320c 100644 --- a/dist/ba_data/python/bastd/game/race.py +++ b/dist/ba_data/python/bastd/game/race.py @@ -25,6 +25,7 @@ if TYPE_CHECKING: @dataclass class RaceMine: """Holds info about a mine on the track.""" + point: Sequence[float] mine: Bomb | None @@ -45,8 +46,9 @@ class RaceRegion(ba.Actor): 'position': pt[:3], 'scale': (pt[3] * 2.0, pt[4] * 2.0, pt[5] * 2.0), 'type': 'box', - 'materials': [activity.race_region_material] - }) + 'materials': [activity.race_region_material], + }, + ) class Player(ba.Player['Team']): @@ -76,13 +78,14 @@ class RaceGame(ba.TeamGameActivity[Player, Team]): name = 'Race' description = 'Run real fast!' - scoreconfig = ba.ScoreConfig(label='Time', - lower_is_better=True, - scoretype=ba.ScoreType.MILLISECONDS) + scoreconfig = ba.ScoreConfig( + label='Time', lower_is_better=True, scoretype=ba.ScoreType.MILLISECONDS + ) @classmethod def get_available_settings( - cls, sessiontype: type[ba.Session]) -> list[ba.Setting]: + cls, sessiontype: type[ba.Session] + ) -> list[ba.Setting]: settings = [ ba.IntSetting('Laps', min_value=1, default=3, increment=1), ba.IntChoiceSetting( @@ -124,7 +127,8 @@ class RaceGame(ba.TeamGameActivity[Player, Team]): # We have some specific settings in teams mode. if issubclass(sessiontype, ba.DualTeamSession): settings.append( - ba.BoolSetting('Entire Team Must Finish', default=False)) + ba.BoolSetting('Entire Team Must Finish', default=False) + ) return settings @classmethod @@ -159,7 +163,8 @@ class RaceGame(ba.TeamGameActivity[Player, Team]): self._bomb_spawn_timer: ba.Timer | None = None self._laps = int(settings['Laps']) self._entire_team_must_finish = bool( - settings.get('Entire Team Must Finish', False)) + settings.get('Entire Team Must Finish', False) + ) self._time_limit = float(settings['Time Limit']) self._mine_spawning = int(settings['Mine Spawning']) self._bomb_spawning = int(settings['Bomb Spawning']) @@ -167,12 +172,15 @@ class RaceGame(ba.TeamGameActivity[Player, Team]): # Base class overrides. self.slow_motion = self._epic_mode - self.default_music = (ba.MusicType.EPIC_RACE - if self._epic_mode else ba.MusicType.RACE) + self.default_music = ( + ba.MusicType.EPIC_RACE if self._epic_mode else ba.MusicType.RACE + ) def get_instance_description(self) -> str | Sequence: - if (isinstance(self.session, ba.DualTeamSession) - and self._entire_team_must_finish): + if ( + isinstance(self.session, ba.DualTeamSession) + and self._entire_team_must_finish + ): t_str = ' Your entire team has to finish.' else: t_str = '' @@ -191,14 +199,14 @@ class RaceGame(ba.TeamGameActivity[Player, Team]): shared = SharedObjects.get() pts = self.map.get_def_points('race_point') mat = self.race_region_material = ba.Material() - mat.add_actions(conditions=('they_have_material', - shared.player_material), - actions=( - ('modify_part_collision', 'collide', True), - ('modify_part_collision', 'physical', False), - ('call', 'at_connect', - self._handle_race_point_collide), - )) + mat.add_actions( + conditions=('they_have_material', shared.player_material), + actions=( + ('modify_part_collision', 'collide', True), + ('modify_part_collision', 'physical', False), + ('call', 'at_connect', self._handle_race_point_collide), + ), + ) for rpt in pts: self._regions.append(RaceRegion(rpt, len(self._regions))) @@ -206,13 +214,15 @@ class RaceGame(ba.TeamGameActivity[Player, Team]): assert isinstance(player.actor, PlayerSpaz) assert player.actor.node pos = player.actor.node.position - light = ba.newnode('light', - attrs={ - 'position': pos, - 'color': (1, 1, 0), - 'height_attenuated': False, - 'radius': 0.4 - }) + light = ba.newnode( + 'light', + attrs={ + 'position': pos, + 'color': (1, 1, 0), + 'height_attenuated': False, + 'radius': 0.4, + }, + ) ba.timer(0.5, light.delete) ba.animate(light, 'intensity', {0: 0, 0.1: 1.0 * scale, 0.5: 0}) @@ -248,11 +258,17 @@ class RaceGame(ba.TeamGameActivity[Player, Team]): if player.is_alive(): assert player.actor player.actor.handlemessage(ba.DieMessage()) - ba.screenmessage(ba.Lstr( - translate=('statements', 'Killing ${NAME} for' - ' skipping part of the track!'), - subs=[('${NAME}', player.getname(full=True))]), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr( + translate=( + 'statements', + 'Killing ${NAME} for' + ' skipping part of the track!', + ), + subs=[('${NAME}', player.getname(full=True))], + ), + color=(1, 0, 0), + ) else: # If this player is in first, note that this is the # front-most race-point. @@ -267,8 +283,10 @@ class RaceGame(ba.TeamGameActivity[Player, Team]): # In teams mode with all-must-finish on, the team lap # value is the min of all team players. # Otherwise its the max. - if isinstance(self.session, ba.DualTeamSession - ) and self._entire_team_must_finish: + if ( + isinstance(self.session, ba.DualTeamSession) + and self._entire_team_must_finish + ): team.lap = min(p.lap for p in team.players) else: team.lap = max(p.lap for p in team.players) @@ -281,9 +299,11 @@ class RaceGame(ba.TeamGameActivity[Player, Team]): if isinstance(self.session, ba.DualTeamSession): assert self._team_finish_pts is not None if self._team_finish_pts > 0: - self.stats.player_scored(player, - self._team_finish_pts, - screenmessage=False) + self.stats.player_scored( + player, + self._team_finish_pts, + screenmessage=False, + ) self._team_finish_pts -= 25 # Flash where the player is. @@ -291,7 +311,8 @@ class RaceGame(ba.TeamGameActivity[Player, Team]): player.finished = True assert player.actor player.actor.handlemessage( - ba.DieMessage(immediate=True)) + ba.DieMessage(immediate=True) + ) # Makes sure noone behind them passes them in rank # while finishing. @@ -318,35 +339,41 @@ class RaceGame(ba.TeamGameActivity[Player, Team]): # Print their lap number over their head. try: assert isinstance(player.actor, PlayerSpaz) - mathnode = ba.newnode('math', - owner=player.actor.node, - attrs={ - 'input1': (0, 1.9, 0), - 'operation': 'add' - }) + mathnode = ba.newnode( + 'math', + owner=player.actor.node, + attrs={ + 'input1': (0, 1.9, 0), + 'operation': 'add', + }, + ) player.actor.node.connectattr( - 'torso_position', mathnode, 'input2') - tstr = ba.Lstr(resource='lapNumberText', - subs=[('${CURRENT}', - str(player.lap + 1)), - ('${TOTAL}', str(self._laps)) - ]) - txtnode = ba.newnode('text', - owner=mathnode, - attrs={ - 'text': tstr, - 'in_world': True, - 'color': (1, 1, 0, 1), - 'scale': 0.015, - 'h_align': 'center' - }) + 'torso_position', mathnode, 'input2' + ) + tstr = ba.Lstr( + resource='lapNumberText', + subs=[ + ('${CURRENT}', str(player.lap + 1)), + ('${TOTAL}', str(self._laps)), + ], + ) + txtnode = ba.newnode( + 'text', + owner=mathnode, + attrs={ + 'text': tstr, + 'in_world': True, + 'color': (1, 1, 0, 1), + 'scale': 0.015, + 'h_align': 'center', + }, + ) mathnode.connectattr('output', txtnode, 'position') - ba.animate(txtnode, 'scale', { - 0.0: 0, - 0.2: 0.019, - 2.0: 0.019, - 2.2: 0 - }) + ba.animate( + txtnode, + 'scale', + {0.0: 0, 0.2: 0.019, 2.0: 0.019, 2.2: 0}, + ) ba.timer(2.3, mathnode.delete) except Exception: ba.print_exception('Error printing lap.') @@ -360,14 +387,23 @@ class RaceGame(ba.TeamGameActivity[Player, Team]): # A player leaving disqualifies the team if 'Entire Team Must Finish' # is on (otherwise in teams mode everyone could just leave except the # leading player to win). - if (isinstance(self.session, ba.DualTeamSession) - and self._entire_team_must_finish): - ba.screenmessage(ba.Lstr( - translate=('statements', - '${TEAM} is disqualified because ${PLAYER} left'), - subs=[('${TEAM}', player.team.name), - ('${PLAYER}', player.getname(full=True))]), - color=(1, 1, 0)) + if ( + isinstance(self.session, ba.DualTeamSession) + and self._entire_team_must_finish + ): + ba.screenmessage( + ba.Lstr( + translate=( + 'statements', + '${TEAM} is disqualified because ${PLAYER} left', + ), + subs=[ + ('${TEAM}', player.team.name), + ('${PLAYER}', player.getname(full=True)), + ], + ), + color=(1, 1, 0), + ) player.team.finished = True player.team.time = None player.team.lap = 0 @@ -390,8 +426,10 @@ class RaceGame(ba.TeamGameActivity[Player, Team]): if not distances: teams_dist = 0.0 else: - if (isinstance(self.session, ba.DualTeamSession) - and self._entire_team_must_finish): + if ( + isinstance(self.session, ba.DualTeamSession) + and self._entire_team_must_finish + ): teams_dist = min(distances) else: teams_dist = max(distances) @@ -400,10 +438,12 @@ class RaceGame(ba.TeamGameActivity[Player, Team]): teams_dist, self._laps, flash=(teams_dist >= float(self._laps)), - show_value=False) + show_value=False, + ) def on_begin(self) -> None: from bastd.actor.onscreentimer import OnScreenTimer + super().on_begin() self.setup_standard_time_limit(self._time_limit) self.setup_standard_powerup_drops() @@ -411,18 +451,21 @@ class RaceGame(ba.TeamGameActivity[Player, Team]): # Throw a timer up on-screen. self._time_text = ba.NodeActor( - ba.newnode('text', - attrs={ - 'v_attach': 'top', - 'h_attach': 'center', - 'h_align': 'center', - 'color': (1, 1, 0.5, 1), - 'flatness': 0.5, - 'shadow': 0.5, - 'position': (0, -50), - 'scale': 1.4, - 'text': '' - })) + ba.newnode( + 'text', + attrs={ + 'v_attach': 'top', + 'h_attach': 'center', + 'h_align': 'center', + 'color': (1, 1, 0.5, 1), + 'flatness': 0.5, + 'shadow': 0.5, + 'position': (0, -50), + 'scale': 1.4, + 'text': '', + }, + ) + ) self._timer = OnScreenTimer() if self._mine_spawning != 0: @@ -431,16 +474,18 @@ class RaceGame(ba.TeamGameActivity[Player, Team]): for p in self.map.get_def_points('race_mine') ] if self._race_mines: - self._race_mine_timer = ba.Timer(0.001 * self._mine_spawning, - self._update_race_mine, - repeat=True) + self._race_mine_timer = ba.Timer( + 0.001 * self._mine_spawning, + self._update_race_mine, + repeat=True, + ) - self._scoreboard_timer = ba.Timer(0.25, - self._update_scoreboard, - repeat=True) - self._player_order_update_timer = ba.Timer(0.25, - self._update_player_order, - repeat=True) + self._scoreboard_timer = ba.Timer( + 0.25, self._update_scoreboard, repeat=True + ) + self._player_order_update_timer = ba.Timer( + 0.25, self._update_player_order, repeat=True + ) if self.slow_motion: t_scale = 0.4 @@ -458,22 +503,27 @@ class RaceGame(ba.TeamGameActivity[Player, Team]): self._start_lights = [] for i in range(4): - lnub = ba.newnode('image', - attrs={ - 'texture': ba.gettexture('nub'), - 'opacity': 1.0, - 'absolute_scale': True, - 'position': (-75 + i * 50, light_y), - 'scale': (50, 50), - 'attach': 'center' - }) + lnub = ba.newnode( + 'image', + attrs={ + 'texture': ba.gettexture('nub'), + 'opacity': 1.0, + 'absolute_scale': True, + 'position': (-75 + i * 50, light_y), + 'scale': (50, 50), + 'attach': 'center', + }, + ) ba.animate( - lnub, 'opacity', { + lnub, + 'opacity', + { 4.0 * t_scale: 0, 5.0 * t_scale: 1.0, 12.0 * t_scale: 1.0, - 12.5 * t_scale: 0.0 - }) + 12.5 * t_scale: 0.0, + }, + ) ba.timer(13.0 * t_scale, lnub.delete) self._start_lights.append(lnub) @@ -512,9 +562,9 @@ class RaceGame(ba.TeamGameActivity[Player, Team]): self._timer.start() if self._bomb_spawning != 0: - self._bomb_spawn_timer = ba.Timer(0.001 * self._bomb_spawning, - self._spawn_bomb, - repeat=True) + self._bomb_spawn_timer = ba.Timer( + 0.001 * self._bomb_spawning, self._spawn_bomb, repeat=True + ) self._race_started = True @@ -531,8 +581,11 @@ class RaceGame(ba.TeamGameActivity[Player, Team]): r_index = player.last_region rg1 = self._regions[r_index] r1pt = ba.Vec3(rg1.pos[:3]) - rg2 = self._regions[0] if r_index == len( - self._regions) - 1 else self._regions[r_index + 1] + rg2 = ( + self._regions[0] + if r_index == len(self._regions) - 1 + else self._regions[r_index + 1] + ) r2pt = ba.Vec3(rg2.pos[:3]) r2dist = (pos - r2pt).length() amt = 1.0 - (r2dist / (r2pt - r1pt).length()) @@ -558,14 +611,24 @@ class RaceGame(ba.TeamGameActivity[Player, Team]): # Don't use the full region so we're less likely to spawn off a cliff. region_scale = 0.8 - x_range = ((-0.5, 0.5) if pos[3] == 0 else - (-region_scale * pos[3], region_scale * pos[3])) - z_range = ((-0.5, 0.5) if pos[5] == 0 else - (-region_scale * pos[5], region_scale * pos[5])) - pos = (pos[0] + random.uniform(*x_range), pos[1] + 1.0, - pos[2] + random.uniform(*z_range)) - ba.timer(random.uniform(0.0, 2.0), - ba.WeakCall(self._spawn_bomb_at_pos, pos)) + x_range = ( + (-0.5, 0.5) + if pos[3] == 0 + else (-region_scale * pos[3], region_scale * pos[3]) + ) + z_range = ( + (-0.5, 0.5) + if pos[5] == 0 + else (-region_scale * pos[5], region_scale * pos[5]) + ) + pos = ( + pos[0] + random.uniform(*x_range), + pos[1] + 1.0, + pos[2] + random.uniform(*z_range), + ) + ba.timer( + random.uniform(0.0, 2.0), ba.WeakCall(self._spawn_bomb_at_pos, pos) + ) def _spawn_bomb_at_pos(self, pos: Sequence[float]) -> None: if self.has_ended(): @@ -581,13 +644,15 @@ class RaceGame(ba.TeamGameActivity[Player, Team]): def _flash_mine(self, i: int) -> None: assert self._race_mines is not None rmine = self._race_mines[i] - light = ba.newnode('light', - attrs={ - 'position': rmine.point[:3], - 'color': (1, 0.2, 0.2), - 'radius': 0.1, - 'height_attenuated': False - }) + light = ba.newnode( + 'light', + attrs={ + 'position': rmine.point[:3], + 'color': (1, 0.2, 0.2), + 'radius': 0.1, + 'height_attenuated': False, + }, + ) ba.animate(light, 'intensity', {0.0: 0, 0.1: 1.0, 0.2: 0}, loop=True) ba.timer(1.0, light.delete) @@ -616,37 +681,48 @@ class RaceGame(ba.TeamGameActivity[Player, Team]): # Don't use the full region so we're less likely to spawn off a cliff. region_scale = 0.8 - x_range = ((-0.5, 0.5) if pos[3] == 0 else - (-region_scale * pos[3], region_scale * pos[3])) - z_range = ((-0.5, 0.5) if pos[5] == 0 else - (-region_scale * pos[5], region_scale * pos[5])) - pos = (pos[0] + random.uniform(*x_range), pos[1], - pos[2] + random.uniform(*z_range)) + x_range = ( + (-0.5, 0.5) + if pos[3] == 0 + else (-region_scale * pos[3], region_scale * pos[3]) + ) + z_range = ( + (-0.5, 0.5) + if pos[5] == 0 + else (-region_scale * pos[5], region_scale * pos[5]) + ) + pos = ( + pos[0] + random.uniform(*x_range), + pos[1], + pos[2] + random.uniform(*z_range), + ) spaz = self.spawn_player_spaz( - player, position=pos, angle=90 if not self._race_started else None) + player, position=pos, angle=90 if not self._race_started else None + ) assert spaz.node # Prevent controlling of characters before the start of the race. if not self._race_started: spaz.disconnect_controls_from_player() - mathnode = ba.newnode('math', - owner=spaz.node, - attrs={ - 'input1': (0, 1.4, 0), - 'operation': 'add' - }) + mathnode = ba.newnode( + 'math', + owner=spaz.node, + attrs={'input1': (0, 1.4, 0), 'operation': 'add'}, + ) spaz.node.connectattr('torso_position', mathnode, 'input2') - distance_txt = ba.newnode('text', - owner=spaz.node, - attrs={ - 'text': '', - 'in_world': True, - 'color': (1, 1, 0.4), - 'scale': 0.02, - 'h_align': 'center' - }) + distance_txt = ba.newnode( + 'text', + owner=spaz.node, + attrs={ + 'text': '', + 'in_world': True, + 'color': (1, 1, 0.4), + 'scale': 0.02, + 'h_align': 'center', + }, + ) player.distance_txt = distance_txt mathnode.connectattr('output', distance_txt, 'position') return spaz @@ -661,7 +737,8 @@ class RaceGame(ba.TeamGameActivity[Player, Team]): # Count the number of teams that have completed the race. teams_completed = len( - [t for t in self.teams if t.finished and t.time is not None]) + [t for t in self.teams if t.finished and t.time is not None] + ) if teams_completed > 0: session = self.session @@ -690,8 +767,10 @@ class RaceGame(ba.TeamGameActivity[Player, Team]): assert self._timer is not None if self._timer.has_started(): self._timer.stop( - endtime=None if self._last_team_time is None else ( - self._timer.getstarttime() + self._last_team_time)) + endtime=None + if self._last_team_time is None + else (self._timer.getstarttime() + self._last_team_time) + ) results = ba.GameResults() @@ -705,9 +784,10 @@ class RaceGame(ba.TeamGameActivity[Player, Team]): # We don't announce a winner in ffa mode since its probably been a # while since the first place guy crossed the finish line so it seems # odd to be announcing that now. - self.end(results=results, - announce_winning_team=isinstance(self.session, - ba.DualTeamSession)) + self.end( + results=results, + announce_winning_team=isinstance(self.session, ba.DualTeamSession), + ) def handlemessage(self, msg: Any) -> Any: if isinstance(msg, ba.PlayerDiedMessage): diff --git a/dist/ba_data/python/bastd/game/runaround.py b/dist/ba_data/python/bastd/game/runaround.py index b80f3ec..d8f5e4b 100644 --- a/dist/ba_data/python/bastd/game/runaround.py +++ b/dist/ba_data/python/bastd/game/runaround.py @@ -23,10 +23,23 @@ from bastd.actor.respawnicon import RespawnIcon from bastd.actor.powerupbox import PowerupBox, PowerupBoxFactory from bastd.gameutils import SharedObjects from bastd.actor.spazbot import ( - SpazBotSet, SpazBot, SpazBotDiedMessage, BomberBot, BrawlerBot, TriggerBot, - TriggerBotPro, BomberBotProShielded, TriggerBotProShielded, ChargerBot, - ChargerBotProShielded, StickyBot, ExplodeyBot, BrawlerBotProShielded, - BomberBotPro, BrawlerBotPro) + SpazBotSet, + SpazBot, + SpazBotDiedMessage, + BomberBot, + BrawlerBot, + TriggerBot, + TriggerBotPro, + BomberBotProShielded, + TriggerBotProShielded, + ChargerBot, + ChargerBotProShielded, + StickyBot, + ExplodeyBot, + BrawlerBotProShielded, + BomberBotPro, + BrawlerBotPro, +) if TYPE_CHECKING: from typing import Any, Sequence @@ -34,6 +47,7 @@ if TYPE_CHECKING: class Preset(Enum): """Play presets.""" + ENDLESS = 'endless' ENDLESS_TOURNAMENT = 'endless_tournament' PRO = 'pro' @@ -46,6 +60,7 @@ class Preset(Enum): class Point(Enum): """Where we can spawn stuff and the corresponding map attr name.""" + BOTTOM_LEFT = 'bot_spawn_bottom_left' BOTTOM_RIGHT = 'bot_spawn_bottom_right' START = 'bot_spawn_start' @@ -54,6 +69,7 @@ class Point(Enum): @dataclass class Spawn: """Defines a bot spawn event.""" + # noinspection PyUnresolvedReferences type: type[SpazBot] path: int = 0 @@ -63,12 +79,14 @@ class Spawn: @dataclass class Spacing: """Defines spacing between spawns.""" + duration: float @dataclass class Wave: """Defines a wave of enemies.""" + entries: list[Spawn | Spacing | None] @@ -92,7 +110,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): tips = [ 'Jump just as you\'re throwing to get bombs up to the highest levels.', 'No, you can\'t get up on the ledge. You have to throw bombs.', - 'Whip back and forth to get more distance on your throws..' + 'Whip back and forth to get more distance on your throws..', ] default_music = ba.MusicType.MARCHING @@ -110,7 +128,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): ChargerBot: 1.0, ChargerBotProShielded: 1.0, ExplodeyBot: 1.0, - StickyBot: 0.5 + StickyBot: 0.5, } def __init__(self, settings: dict): @@ -134,7 +152,8 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): self._powerup_center = self._map_type.defs.boxes['powerup_region'][0:3] self._powerup_spread = ( self._map_type.defs.boxes['powerup_region'][6] * 0.5, - self._map_type.defs.boxes['powerup_region'][8] * 0.5) + self._map_type.defs.boxes['powerup_region'][8] * 0.5, + ) self._score_region_material = ba.Material() self._score_region_material.add_actions( @@ -143,7 +162,8 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): ('modify_part_collision', 'collide', True), ('modify_part_collision', 'physical', False), ('call', 'at_connect', self._handle_reached_end), - )) + ), + ) self._last_wave_end_time = ba.time() self._player_has_picked_up_powerup = False @@ -175,8 +195,9 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): def on_transition_in(self) -> None: super().on_transition_in() - self._scoreboard = Scoreboard(label=ba.Lstr(resource='scoreText'), - score_split=0.5) + self._scoreboard = Scoreboard( + label=ba.Lstr(resource='scoreText'), score_split=0.5 + ) self._score_region = ba.NodeActor( ba.newnode( 'region', @@ -184,8 +205,10 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): 'position': self.map.defs.boxes['score_region'][0:3], 'scale': self.map.defs.boxes['score_region'][6:9], 'type': 'box', - 'materials': [self._score_region_material] - })) + 'materials': [self._score_region_material], + }, + ) + ) def on_begin(self) -> None: super().on_begin() @@ -196,178 +219,222 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): self._exclude_powerups = ['curse'] self._have_tnt = True self._waves = [ - Wave(entries=[ - Spawn(BomberBot, path=3 if hard else 2), - Spawn(BomberBot, path=2), - Spawn(BomberBot, path=2) if hard else None, - Spawn(BomberBot, path=2) if player_count > 1 else None, - Spawn(BomberBot, path=1) if hard else None, - Spawn(BomberBot, path=1) if player_count > 2 else None, - Spawn(BomberBot, path=1) if player_count > 3 else None, - ]), - Wave(entries=[ - Spawn(BomberBot, path=1) if hard else None, - Spawn(BomberBot, path=2) if hard else None, - Spawn(BomberBot, path=2), - Spawn(BomberBot, path=2), - Spawn(BomberBot, path=2) if player_count > 3 else None, - Spawn(BrawlerBot, path=3), - Spawn(BrawlerBot, path=3), - Spawn(BrawlerBot, path=3) if hard else None, - Spawn(BrawlerBot, path=3) if player_count > 1 else None, - Spawn(BrawlerBot, path=3) if player_count > 2 else None, - ]), - Wave(entries=[ - Spawn(ChargerBot, path=2) if hard else None, - Spawn(ChargerBot, path=2) if player_count > 2 else None, - Spawn(TriggerBot, path=2), - Spawn(TriggerBot, path=2) if player_count > 1 else None, - Spacing(duration=3.0), - Spawn(BomberBot, path=2) if hard else None, - Spawn(BomberBot, path=2) if hard else None, - Spawn(BomberBot, path=2), - Spawn(BomberBot, path=3) if hard else None, - Spawn(BomberBot, path=3), - Spawn(BomberBot, path=3), - Spawn(BomberBot, path=3) if player_count > 3 else None, - ]), - Wave(entries=[ - Spawn(TriggerBot, path=1) if hard else None, - Spacing(duration=1.0) if hard else None, - Spawn(TriggerBot, path=2), - Spacing(duration=1.0), - Spawn(TriggerBot, path=3), - Spacing(duration=1.0), - Spawn(TriggerBot, path=1) if hard else None, - Spacing(duration=1.0) if hard else None, - Spawn(TriggerBot, path=2), - Spacing(duration=1.0), - Spawn(TriggerBot, path=3), - Spacing(duration=1.0), - Spawn(TriggerBot, path=1) if ( - player_count > 1 and hard) else None, - Spacing(duration=1.0), - Spawn(TriggerBot, path=2) if player_count > 2 else None, - Spacing(duration=1.0), - Spawn(TriggerBot, path=3) if player_count > 3 else None, - Spacing(duration=1.0), - ]), - Wave(entries=[ - Spawn(ChargerBotProShielded if hard else ChargerBot, - path=1), - Spawn(BrawlerBot, path=2) if hard else None, - Spawn(BrawlerBot, path=2), - Spawn(BrawlerBot, path=2), - Spawn(BrawlerBot, path=3) if hard else None, - Spawn(BrawlerBot, path=3), - Spawn(BrawlerBot, path=3), - Spawn(BrawlerBot, path=3) if player_count > 1 else None, - Spawn(BrawlerBot, path=3) if player_count > 2 else None, - Spawn(BrawlerBot, path=3) if player_count > 3 else None, - ]), - Wave(entries=[ - Spawn(BomberBotProShielded, path=3), - Spacing(duration=1.5), - Spawn(BomberBotProShielded, path=2), - Spacing(duration=1.5), - Spawn(BomberBotProShielded, path=1) if hard else None, - Spacing(duration=1.0) if hard else None, - Spawn(BomberBotProShielded, path=3), - Spacing(duration=1.5), - Spawn(BomberBotProShielded, path=2), - Spacing(duration=1.5), - Spawn(BomberBotProShielded, path=1) if hard else None, - Spacing(duration=1.5) if hard else None, - Spawn(BomberBotProShielded, path=3 - ) if player_count > 1 else None, - Spacing(duration=1.5), - Spawn(BomberBotProShielded, path=2 - ) if player_count > 2 else None, - Spacing(duration=1.5), - Spawn(BomberBotProShielded, path=1 - ) if player_count > 3 else None, - ]), + Wave( + entries=[ + Spawn(BomberBot, path=3 if hard else 2), + Spawn(BomberBot, path=2), + Spawn(BomberBot, path=2) if hard else None, + Spawn(BomberBot, path=2) if player_count > 1 else None, + Spawn(BomberBot, path=1) if hard else None, + Spawn(BomberBot, path=1) if player_count > 2 else None, + Spawn(BomberBot, path=1) if player_count > 3 else None, + ] + ), + Wave( + entries=[ + Spawn(BomberBot, path=1) if hard else None, + Spawn(BomberBot, path=2) if hard else None, + Spawn(BomberBot, path=2), + Spawn(BomberBot, path=2), + Spawn(BomberBot, path=2) if player_count > 3 else None, + Spawn(BrawlerBot, path=3), + Spawn(BrawlerBot, path=3), + Spawn(BrawlerBot, path=3) if hard else None, + Spawn(BrawlerBot, path=3) if player_count > 1 else None, + Spawn(BrawlerBot, path=3) if player_count > 2 else None, + ] + ), + Wave( + entries=[ + Spawn(ChargerBot, path=2) if hard else None, + Spawn(ChargerBot, path=2) if player_count > 2 else None, + Spawn(TriggerBot, path=2), + Spawn(TriggerBot, path=2) if player_count > 1 else None, + Spacing(duration=3.0), + Spawn(BomberBot, path=2) if hard else None, + Spawn(BomberBot, path=2) if hard else None, + Spawn(BomberBot, path=2), + Spawn(BomberBot, path=3) if hard else None, + Spawn(BomberBot, path=3), + Spawn(BomberBot, path=3), + Spawn(BomberBot, path=3) if player_count > 3 else None, + ] + ), + Wave( + entries=[ + Spawn(TriggerBot, path=1) if hard else None, + Spacing(duration=1.0) if hard else None, + Spawn(TriggerBot, path=2), + Spacing(duration=1.0), + Spawn(TriggerBot, path=3), + Spacing(duration=1.0), + Spawn(TriggerBot, path=1) if hard else None, + Spacing(duration=1.0) if hard else None, + Spawn(TriggerBot, path=2), + Spacing(duration=1.0), + Spawn(TriggerBot, path=3), + Spacing(duration=1.0), + Spawn(TriggerBot, path=1) + if (player_count > 1 and hard) + else None, + Spacing(duration=1.0), + Spawn(TriggerBot, path=2) if player_count > 2 else None, + Spacing(duration=1.0), + Spawn(TriggerBot, path=3) if player_count > 3 else None, + Spacing(duration=1.0), + ] + ), + Wave( + entries=[ + Spawn( + ChargerBotProShielded if hard else ChargerBot, + path=1, + ), + Spawn(BrawlerBot, path=2) if hard else None, + Spawn(BrawlerBot, path=2), + Spawn(BrawlerBot, path=2), + Spawn(BrawlerBot, path=3) if hard else None, + Spawn(BrawlerBot, path=3), + Spawn(BrawlerBot, path=3), + Spawn(BrawlerBot, path=3) if player_count > 1 else None, + Spawn(BrawlerBot, path=3) if player_count > 2 else None, + Spawn(BrawlerBot, path=3) if player_count > 3 else None, + ] + ), + Wave( + entries=[ + Spawn(BomberBotProShielded, path=3), + Spacing(duration=1.5), + Spawn(BomberBotProShielded, path=2), + Spacing(duration=1.5), + Spawn(BomberBotProShielded, path=1) if hard else None, + Spacing(duration=1.0) if hard else None, + Spawn(BomberBotProShielded, path=3), + Spacing(duration=1.5), + Spawn(BomberBotProShielded, path=2), + Spacing(duration=1.5), + Spawn(BomberBotProShielded, path=1) if hard else None, + Spacing(duration=1.5) if hard else None, + Spawn(BomberBotProShielded, path=3) + if player_count > 1 + else None, + Spacing(duration=1.5), + Spawn(BomberBotProShielded, path=2) + if player_count > 2 + else None, + Spacing(duration=1.5), + Spawn(BomberBotProShielded, path=1) + if player_count > 3 + else None, + ] + ), ] elif self._preset in { - Preset.UBER_EASY, Preset.UBER, Preset.TOURNAMENT_UBER + Preset.UBER_EASY, + Preset.UBER, + Preset.TOURNAMENT_UBER, }: self._exclude_powerups = [] self._have_tnt = True self._waves = [ - Wave(entries=[ - Spawn(TriggerBot, path=1) if hard else None, - Spawn(TriggerBot, path=2), - Spawn(TriggerBot, path=2), - Spawn(TriggerBot, path=3), - Spawn(BrawlerBotPro if hard else BrawlerBot, - point=Point.BOTTOM_LEFT), - Spawn(BrawlerBotPro, point=Point.BOTTOM_RIGHT - ) if player_count > 2 else None, - ]), - Wave(entries=[ - Spawn(ChargerBot, path=2), - Spawn(ChargerBot, path=3), - Spawn(ChargerBot, path=1) if hard else None, - Spawn(ChargerBot, path=2), - Spawn(ChargerBot, path=3), - Spawn(ChargerBot, path=1) if player_count > 2 else None, - ]), - Wave(entries=[ - Spawn(BomberBotProShielded, path=1) if hard else None, - Spawn(BomberBotProShielded, path=2), - Spawn(BomberBotProShielded, path=2), - Spawn(BomberBotProShielded, path=3), - Spawn(BomberBotProShielded, path=3), - Spawn(ChargerBot, point=Point.BOTTOM_RIGHT), - Spawn(ChargerBot, point=Point.BOTTOM_LEFT - ) if player_count > 2 else None, - ]), - Wave(entries=[ - Spawn(TriggerBotPro, path=1) if hard else None, - Spawn(TriggerBotPro, path=1 if hard else 2), - Spawn(TriggerBotPro, path=1 if hard else 2), - Spawn(TriggerBotPro, path=1 if hard else 2), - Spawn(TriggerBotPro, path=1 if hard else 2), - Spawn(TriggerBotPro, path=1 if hard else 2), - Spawn(TriggerBotPro, path=1 if hard else 2 - ) if player_count > 1 else None, - Spawn(TriggerBotPro, path=1 if hard else 2 - ) if player_count > 3 else None, - ]), - Wave(entries=[ - Spawn(TriggerBotProShielded if hard else TriggerBotPro, - point=Point.BOTTOM_LEFT), - Spawn(TriggerBotProShielded, point=Point.BOTTOM_RIGHT - ) if hard else None, - Spawn(TriggerBotProShielded, point=Point.BOTTOM_RIGHT - ) if player_count > 2 else None, - Spawn(BomberBot, path=3), - Spawn(BomberBot, path=3), - Spacing(duration=5.0), - Spawn(BrawlerBot, path=2), - Spawn(BrawlerBot, path=2), - Spacing(duration=5.0), - Spawn(TriggerBot, path=1) if hard else None, - Spawn(TriggerBot, path=1) if hard else None, - ]), - Wave(entries=[ - Spawn(BomberBotProShielded, path=2), - Spawn(BomberBotProShielded, path=2) if hard else None, - Spawn(StickyBot, point=Point.BOTTOM_RIGHT), - Spawn(BomberBotProShielded, path=2), - Spawn(BomberBotProShielded, path=2), - Spawn(StickyBot, point=Point.BOTTOM_RIGHT - ) if player_count > 2 else None, - Spawn(BomberBotProShielded, path=2), - Spawn(ExplodeyBot, point=Point.BOTTOM_LEFT), - Spawn(BomberBotProShielded, path=2), - Spawn(BomberBotProShielded, path=2 - ) if player_count > 1 else None, - Spacing(duration=5.0), - Spawn(StickyBot, point=Point.BOTTOM_LEFT), - Spacing(duration=2.0), - Spawn(ExplodeyBot, point=Point.BOTTOM_RIGHT), - ]), + Wave( + entries=[ + Spawn(TriggerBot, path=1) if hard else None, + Spawn(TriggerBot, path=2), + Spawn(TriggerBot, path=2), + Spawn(TriggerBot, path=3), + Spawn( + BrawlerBotPro if hard else BrawlerBot, + point=Point.BOTTOM_LEFT, + ), + Spawn(BrawlerBotPro, point=Point.BOTTOM_RIGHT) + if player_count > 2 + else None, + ] + ), + Wave( + entries=[ + Spawn(ChargerBot, path=2), + Spawn(ChargerBot, path=3), + Spawn(ChargerBot, path=1) if hard else None, + Spawn(ChargerBot, path=2), + Spawn(ChargerBot, path=3), + Spawn(ChargerBot, path=1) if player_count > 2 else None, + ] + ), + Wave( + entries=[ + Spawn(BomberBotProShielded, path=1) if hard else None, + Spawn(BomberBotProShielded, path=2), + Spawn(BomberBotProShielded, path=2), + Spawn(BomberBotProShielded, path=3), + Spawn(BomberBotProShielded, path=3), + Spawn(ChargerBot, point=Point.BOTTOM_RIGHT), + Spawn(ChargerBot, point=Point.BOTTOM_LEFT) + if player_count > 2 + else None, + ] + ), + Wave( + entries=[ + Spawn(TriggerBotPro, path=1) if hard else None, + Spawn(TriggerBotPro, path=1 if hard else 2), + Spawn(TriggerBotPro, path=1 if hard else 2), + Spawn(TriggerBotPro, path=1 if hard else 2), + Spawn(TriggerBotPro, path=1 if hard else 2), + Spawn(TriggerBotPro, path=1 if hard else 2), + Spawn(TriggerBotPro, path=1 if hard else 2) + if player_count > 1 + else None, + Spawn(TriggerBotPro, path=1 if hard else 2) + if player_count > 3 + else None, + ] + ), + Wave( + entries=[ + Spawn( + TriggerBotProShielded if hard else TriggerBotPro, + point=Point.BOTTOM_LEFT, + ), + Spawn(TriggerBotProShielded, point=Point.BOTTOM_RIGHT) + if hard + else None, + Spawn(TriggerBotProShielded, point=Point.BOTTOM_RIGHT) + if player_count > 2 + else None, + Spawn(BomberBot, path=3), + Spawn(BomberBot, path=3), + Spacing(duration=5.0), + Spawn(BrawlerBot, path=2), + Spawn(BrawlerBot, path=2), + Spacing(duration=5.0), + Spawn(TriggerBot, path=1) if hard else None, + Spawn(TriggerBot, path=1) if hard else None, + ] + ), + Wave( + entries=[ + Spawn(BomberBotProShielded, path=2), + Spawn(BomberBotProShielded, path=2) if hard else None, + Spawn(StickyBot, point=Point.BOTTOM_RIGHT), + Spawn(BomberBotProShielded, path=2), + Spawn(BomberBotProShielded, path=2), + Spawn(StickyBot, point=Point.BOTTOM_RIGHT) + if player_count > 2 + else None, + Spawn(BomberBotProShielded, path=2), + Spawn(ExplodeyBot, point=Point.BOTTOM_LEFT), + Spawn(BomberBotProShielded, path=2), + Spawn(BomberBotProShielded, path=2) + if player_count > 1 + else None, + Spacing(duration=5.0), + Spawn(StickyBot, point=Point.BOTTOM_LEFT), + Spacing(duration=2.0), + Spawn(ExplodeyBot, point=Point.BOTTOM_RIGHT), + ] + ), ] elif self._preset in {Preset.ENDLESS, Preset.ENDLESS_TOURNAMENT}: self._exclude_powerups = [] @@ -385,20 +452,28 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): # Make sure to stay out of the way of menu/party buttons in the corner. uiscale = ba.app.ui.uiscale - l_offs = (-80 if uiscale is ba.UIScale.SMALL else - -40 if uiscale is ba.UIScale.MEDIUM else 0) + l_offs = ( + -80 + if uiscale is ba.UIScale.SMALL + else -40 + if uiscale is ba.UIScale.MEDIUM + else 0 + ) self._lives_bg = ba.NodeActor( - ba.newnode('image', - attrs={ - 'texture': self._heart_tex, - 'model_opaque': self._heart_model_opaque, - 'model_transparent': self._heart_model_transparent, - 'attach': 'topRight', - 'scale': (90, 90), - 'position': (-110 + l_offs, -50), - 'color': (1, 0.2, 0.2) - })) + ba.newnode( + 'image', + attrs={ + 'texture': self._heart_tex, + 'model_opaque': self._heart_model_opaque, + 'model_transparent': self._heart_model_transparent, + 'attach': 'topRight', + 'scale': (90, 90), + 'position': (-110 + l_offs, -50), + 'color': (1, 0.2, 0.2), + }, + ) + ) # FIXME; should not set things based on vr mode. # (won't look right to non-vr connected clients, etc) vrmode = ba.app.vr_mode @@ -415,8 +490,10 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): 'vr_depth': 10, 'position': (-113 + l_offs, -69), 'scale': 1.3, - 'text': str(self._lives) - })) + 'text': str(self._lives), + }, + ) + ) ba.timer(2.0, self._start_updating_waves) @@ -428,16 +505,14 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): self._flawless = False pos = spaz.node.position ba.playsound(self._bad_guy_score_sound, position=pos) - light = ba.newnode('light', - attrs={ - 'position': pos, - 'radius': 0.5, - 'color': (1, 0, 0) - }) + light = ba.newnode( + 'light', attrs={'position': pos, 'radius': 0.5, 'color': (1, 0, 0)} + ) ba.animate(light, 'intensity', {0.0: 0, 0.1: 1, 0.5: 0}, loop=False) ba.timer(1.0, light.delete) spaz.handlemessage( - ba.DieMessage(immediate=True, how=ba.DeathType.REACHED_GOAL)) + ba.DieMessage(immediate=True, how=ba.DeathType.REACHED_GOAL) + ) if self._lives > 0: self._lives -= 1 @@ -456,26 +531,43 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): for _i in range(4): ba.timer( delay, - ba.Call(_safesetattr, self._lives_text.node, 'color', - (1, 0, 0, 1.0))) + ba.Call( + _safesetattr, + self._lives_text.node, + 'color', + (1, 0, 0, 1.0), + ), + ) assert self._lives_bg is not None assert self._lives_bg.node ba.timer( delay, - ba.Call(_safesetattr, self._lives_bg.node, 'opacity', 0.5)) + ba.Call(_safesetattr, self._lives_bg.node, 'opacity', 0.5), + ) delay += 0.125 ba.timer( delay, - ba.Call(_safesetattr, self._lives_text.node, 'color', - (1.0, 1.0, 0.0, 1.0))) + ba.Call( + _safesetattr, + self._lives_text.node, + 'color', + (1.0, 1.0, 0.0, 1.0), + ), + ) ba.timer( delay, - ba.Call(_safesetattr, self._lives_bg.node, 'opacity', 1.0)) + ba.Call(_safesetattr, self._lives_bg.node, 'opacity', 1.0), + ) delay += 0.125 ba.timer( delay, - ba.Call(_safesetattr, self._lives_text.node, 'color', - (0.8, 0.8, 0.8, 1.0))) + ba.Call( + _safesetattr, + self._lives_text.node, + 'color', + (0.8, 0.8, 0.8, 1.0), + ), + ) def on_continue(self) -> None: self._lives = 3 @@ -485,9 +577,11 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): self._bots.start_moving() def spawn_player(self, player: Player) -> ba.Actor: - pos = (self._spawn_center[0] + random.uniform(-1.5, 1.5), - self._spawn_center[1], - self._spawn_center[2] + random.uniform(-1.5, 1.5)) + pos = ( + self._spawn_center[0] + random.uniform(-1.5, 1.5), + self._spawn_center[1], + self._spawn_center[2] + random.uniform(-1.5, 1.5), + ) spaz = self.spawn_player_spaz(player, position=pos) if self._preset in {Preset.PRO_EASY, Preset.UBER_EASY}: spaz.impact_scale = 0.25 @@ -500,21 +594,22 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): del player # Unused. self._player_has_picked_up_powerup = True - def _drop_powerup(self, - index: int, - poweruptype: str | None = None) -> None: + def _drop_powerup(self, index: int, poweruptype: str | None = None) -> None: if poweruptype is None: - poweruptype = (PowerupBoxFactory.get().get_random_powerup_type( - excludetypes=self._exclude_powerups)) - PowerupBox(position=self.map.powerup_spawn_points[index], - poweruptype=poweruptype).autoretain() + poweruptype = PowerupBoxFactory.get().get_random_powerup_type( + excludetypes=self._exclude_powerups + ) + PowerupBox( + position=self.map.powerup_spawn_points[index], + poweruptype=poweruptype, + ).autoretain() def _start_powerup_drops(self) -> None: ba.timer(3.0, self._drop_powerups, repeat=True) - def _drop_powerups(self, - standard_points: bool = False, - force_first: str | None = None) -> None: + def _drop_powerups( + self, standard_points: bool = False, force_first: str | None = None + ) -> None: """Generic powerup drop.""" # If its been a minute since our last wave finished emerging, stop @@ -530,22 +625,32 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): for i in range(len(points)): ba.timer( 1.0 + i * 0.5, - ba.Call(self._drop_powerup, i, - force_first if i == 0 else None)) + ba.Call( + self._drop_powerup, i, force_first if i == 0 else None + ), + ) else: - pos = (self._powerup_center[0] + random.uniform( - -1.0 * self._powerup_spread[0], 1.0 * self._powerup_spread[0]), - self._powerup_center[1], - self._powerup_center[2] + random.uniform( - -self._powerup_spread[1], self._powerup_spread[1])) + pos = ( + self._powerup_center[0] + + random.uniform( + -1.0 * self._powerup_spread[0], + 1.0 * self._powerup_spread[0], + ), + self._powerup_center[1], + self._powerup_center[2] + + random.uniform( + -self._powerup_spread[1], self._powerup_spread[1] + ), + ) # drop one random one somewhere.. assert self._exclude_powerups is not None PowerupBox( position=pos, poweruptype=PowerupBoxFactory.get().get_random_powerup_type( - excludetypes=self._exclude_powerups + - extra_excludes)).autoretain() + excludetypes=self._exclude_powerups + extra_excludes + ), + ).autoretain() def end_game(self) -> None: ba.pushcall(ba.Call(self.do_end, 'defeat')) @@ -569,13 +674,15 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): score = None fail_message = ba.Lstr(resource='reachWave2Text') - self.end(delay=delay, - results={ - 'outcome': outcome, - 'score': score, - 'fail_message': fail_message, - 'playerinfos': self.initialplayerinfos - }) + self.end( + delay=delay, + results={ + 'outcome': outcome, + 'score': score, + 'fail_message': fail_message, + 'playerinfos': self.initialplayerinfos, + }, + ) def _on_got_scores_to_beat(self, scores: list[dict[str, Any]]) -> None: self._show_standard_scores_to_beat_ui(scores) @@ -584,8 +691,12 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): # pylint: disable=too-many-branches # If we have no living bots, go to the next wave. - if (self._can_end_wave and not self._bots.have_living_bots() - and not self._game_over and self._lives > 0): + if ( + self._can_end_wave + and not self._bots.have_living_bots() + and not self._game_over + and self._lives > 0 + ): self._can_end_wave = False self._time_bonus_timer = None @@ -595,14 +706,16 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): won = False else: assert self._waves is not None - won = (self._wavenum == len(self._waves)) + won = self._wavenum == len(self._waves) # Reward time bonus. base_delay = 4.0 if won else 0 if self._time_bonus > 0: ba.timer(0, ba.Call(ba.playsound, self._cashregistersound)) - ba.timer(base_delay, - ba.Call(self._award_time_bonus, self._time_bonus)) + ba.timer( + base_delay, + ba.Call(self._award_time_bonus, self._time_bonus), + ) base_delay += 1.0 # Reward flawless bonus. @@ -616,25 +729,28 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): # Completion achievements: if self._preset in {Preset.PRO, Preset.PRO_EASY}: - self._award_achievement('Pro Runaround Victory', - sound=False) + self._award_achievement( + 'Pro Runaround Victory', sound=False + ) if self._lives == self._start_lives: self._award_achievement('The Wall', sound=False) if not self._player_has_picked_up_powerup: - self._award_achievement('Precision Bombing', - sound=False) + self._award_achievement( + 'Precision Bombing', sound=False + ) elif self._preset in {Preset.UBER, Preset.UBER_EASY}: - self._award_achievement('Uber Runaround Victory', - sound=False) + self._award_achievement( + 'Uber Runaround Victory', sound=False + ) if self._lives == self._start_lives: self._award_achievement('The Great Wall', sound=False) if not self._a_player_has_been_killed: self._award_achievement('Stayin\' Alive', sound=False) # Give remaining players some points and have them celebrate. - self.show_zoom_message(ba.Lstr(resource='victoryText'), - scale=1.0, - duration=4.0) + self.show_zoom_message( + ba.Lstr(resource='victoryText'), scale=1.0, duration=4.0 + ) self.celebrate(10.0) ba.timer(base_delay, self._award_lives_bonus) @@ -659,71 +775,98 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): def _award_completion_bonus(self) -> None: bonus = 200 ba.playsound(self._cashregistersound) - PopupText(ba.Lstr(value='+${A} ${B}', - subs=[('${A}', str(bonus)), - ('${B}', - ba.Lstr(resource='completionBonusText'))]), - color=(0.7, 0.7, 1.0, 1), - scale=1.6, - position=(0, 1.5, -1)).autoretain() + PopupText( + ba.Lstr( + value='+${A} ${B}', + subs=[ + ('${A}', str(bonus)), + ('${B}', ba.Lstr(resource='completionBonusText')), + ], + ), + color=(0.7, 0.7, 1.0, 1), + scale=1.6, + position=(0, 1.5, -1), + ).autoretain() self._score += bonus self._update_scores() def _award_lives_bonus(self) -> None: bonus = self._lives * 30 ba.playsound(self._cashregistersound) - PopupText(ba.Lstr(value='+${A} ${B}', - subs=[('${A}', str(bonus)), - ('${B}', ba.Lstr(resource='livesBonusText'))]), - color=(0.7, 1.0, 0.3, 1), - scale=1.3, - position=(0, 1, -1)).autoretain() + PopupText( + ba.Lstr( + value='+${A} ${B}', + subs=[ + ('${A}', str(bonus)), + ('${B}', ba.Lstr(resource='livesBonusText')), + ], + ), + color=(0.7, 1.0, 0.3, 1), + scale=1.3, + position=(0, 1, -1), + ).autoretain() self._score += bonus self._update_scores() def _award_time_bonus(self, bonus: int) -> None: ba.playsound(self._cashregistersound) - PopupText(ba.Lstr(value='+${A} ${B}', - subs=[('${A}', str(bonus)), - ('${B}', ba.Lstr(resource='timeBonusText'))]), - color=(1, 1, 0.5, 1), - scale=1.0, - position=(0, 3, -1)).autoretain() + PopupText( + ba.Lstr( + value='+${A} ${B}', + subs=[ + ('${A}', str(bonus)), + ('${B}', ba.Lstr(resource='timeBonusText')), + ], + ), + color=(1, 1, 0.5, 1), + scale=1.0, + position=(0, 3, -1), + ).autoretain() self._score += self._time_bonus self._update_scores() def _award_flawless_bonus(self) -> None: ba.playsound(self._cashregistersound) - PopupText(ba.Lstr(value='+${A} ${B}', - subs=[('${A}', str(self._flawless_bonus)), - ('${B}', ba.Lstr(resource='perfectWaveText')) - ]), - color=(1, 1, 0.2, 1), - scale=1.2, - position=(0, 2, -1)).autoretain() + PopupText( + ba.Lstr( + value='+${A} ${B}', + subs=[ + ('${A}', str(self._flawless_bonus)), + ('${B}', ba.Lstr(resource='perfectWaveText')), + ], + ), + color=(1, 1, 0.2, 1), + scale=1.2, + position=(0, 2, -1), + ).autoretain() assert self._flawless_bonus is not None self._score += self._flawless_bonus self._update_scores() def _start_time_bonus_timer(self) -> None: - self._time_bonus_timer = ba.Timer(1.0, - self._update_time_bonus, - repeat=True) + self._time_bonus_timer = ba.Timer( + 1.0, self._update_time_bonus, repeat=True + ) def _start_next_wave(self) -> None: # FIXME: Need to split this up. # pylint: disable=too-many-locals # pylint: disable=too-many-branches # pylint: disable=too-many-statements - self.show_zoom_message(ba.Lstr(value='${A} ${B}', - subs=[('${A}', - ba.Lstr(resource='waveText')), - ('${B}', str(self._wavenum))]), - scale=1.0, - duration=1.0, - trail=True) + self.show_zoom_message( + ba.Lstr( + value='${A} ${B}', + subs=[ + ('${A}', ba.Lstr(resource='waveText')), + ('${B}', str(self._wavenum)), + ], + ), + scale=1.0, + duration=1.0, + trail=True, + ) ba.timer(0.4, ba.Call(ba.playsound, self._new_wave_sound)) t_sec = 0.0 base_delay = 0.5 @@ -745,16 +888,19 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): if level > 5: spaz_types += [(TriggerBotPro, 7.5)] * (1 + (level - 5) // 7) if level > 2: - spaz_types += [(BomberBotProShielded, 8.0) - ] * (1 + (level - 2) // 6) + spaz_types += [(BomberBotProShielded, 8.0)] * ( + 1 + (level - 2) // 6 + ) if level > 6: - spaz_types += [(TriggerBotProShielded, 12.0) - ] * (1 + (level - 6) // 5) + spaz_types += [(TriggerBotProShielded, 12.0)] * ( + 1 + (level - 6) // 5 + ) if level > 1: - spaz_types += ([(ChargerBot, 10.0)] * (1 + (level - 1) // 4)) + spaz_types += [(ChargerBot, 10.0)] * (1 + (level - 1) // 4) if level > 7: - spaz_types += [(ChargerBotProShielded, 15.0) - ] * (1 + (level - 7) // 3) + spaz_types += [(ChargerBotProShielded, 15.0)] * ( + 1 + (level - 7) // 3 + ) # Bot type, their effect on target points. defender_types: list[tuple[type[SpazBot], float]] = [ @@ -765,16 +911,17 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): if level > 2: defender_types += [(ChargerBot, 0.75)] if level > 4: - defender_types += ([(StickyBot, 0.7)] * (1 + (level - 5) // 6)) + defender_types += [(StickyBot, 0.7)] * (1 + (level - 5) // 6) if level > 6: - defender_types += ([(ExplodeyBot, 0.7)] * (1 + - (level - 5) // 5)) + defender_types += [(ExplodeyBot, 0.7)] * (1 + (level - 5) // 5) if level > 8: - defender_types += ([(BrawlerBotProShielded, 0.65)] * - (1 + (level - 5) // 4)) + defender_types += [(BrawlerBotProShielded, 0.65)] * ( + 1 + (level - 5) // 4 + ) if level > 10: - defender_types += ([(TriggerBotProShielded, 0.6)] * - (1 + (level - 6) // 3)) + defender_types += [(TriggerBotProShielded, 0.6)] * ( + 1 + (level - 6) // 3 + ) for group in range(group_count): this_target_point_s = target_points / group_count @@ -821,31 +968,41 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): elif path == 6: this_target_point_s *= 0.7 - def _add_defender(defender_type: tuple[type[SpazBot], float], - pnt: Point) -> tuple[float, Spawn]: + def _add_defender( + defender_type: tuple[type[SpazBot], float], pnt: Point + ) -> tuple[float, Spawn]: # This is ok because we call it immediately. # pylint: disable=cell-var-from-loop return this_target_point_s * defender_type[1], Spawn( - defender_type[0], point=pnt) + defender_type[0], point=pnt + ) # Add defenders. - defender_type1 = defender_types[random.randrange( - len(defender_types))] - defender_type2 = defender_types[random.randrange( - len(defender_types))] + defender_type1 = defender_types[ + random.randrange(len(defender_types)) + ] + defender_type2 = defender_types[ + random.randrange(len(defender_types)) + ] defender1 = defender2 = None - if ((group == 0) or (group == 1 and level > 3) - or (group == 2 and level > 5)): + if ( + (group == 0) + or (group == 1 and level > 3) + or (group == 2 and level > 5) + ): if random.random() < min(0.75, (level - 1) * 0.11): this_target_point_s, defender1 = _add_defender( - defender_type1, Point.BOTTOM_LEFT) + defender_type1, Point.BOTTOM_LEFT + ) if random.random() < min(0.75, (level - 1) * 0.04): this_target_point_s, defender2 = _add_defender( - defender_type2, Point.BOTTOM_RIGHT) + defender_type2, Point.BOTTOM_RIGHT + ) spaz_type = spaz_types[random.randrange(len(spaz_types))] member_count = max( - 1, int(round(this_target_point_s / spaz_type[1]))) + 1, int(round(this_target_point_s / spaz_type[1])) + ) for i, _member in enumerate(range(member_count)): if path == 4: this_path = i % 3 # Looping forward. @@ -907,14 +1064,20 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): delay /= self._get_bot_speed(bot_type) t_sec += delay * 0.5 tcall = ba.Call( - self.add_bot_at_point, point, bot_type, path, - 0.1 if point is Point.START else non_runner_spawn_time) + self.add_bot_at_point, + point, + bot_type, + path, + 0.1 if point is Point.START else non_runner_spawn_time, + ) ba.timer(t_sec, tcall) t_sec += delay * 0.5 # We can end the wave after all the spawning happens. - ba.timer(t_sec - delay * 0.5 + non_runner_spawn_time + 0.01, - self._set_can_end_wave) + ba.timer( + t_sec - delay * 0.5 + non_runner_spawn_time + 0.01, + self._set_can_end_wave, + ) # Reset our time bonus. # In this game we use a constant time bonus so it erodes away in @@ -926,23 +1089,28 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): assert self._time_bonus_mult is not None txtval = ba.Lstr( value='${A}: ${B}', - subs=[('${A}', ba.Lstr(resource='timeBonusText')), - ('${B}', str(int(self._time_bonus * self._time_bonus_mult))) - ]) + subs=[ + ('${A}', ba.Lstr(resource='timeBonusText')), + ('${B}', str(int(self._time_bonus * self._time_bonus_mult))), + ], + ) self._time_bonus_text = ba.NodeActor( - ba.newnode('text', - attrs={ - 'v_attach': 'top', - 'h_attach': 'center', - 'h_align': 'center', - 'color': (1, 1, 0.0, 1), - 'shadow': 1.0, - 'vr_depth': -30, - 'flatness': 1.0, - 'position': (0, -60), - 'scale': 0.8, - 'text': txtval - })) + ba.newnode( + 'text', + attrs={ + 'v_attach': 'top', + 'h_attach': 'center', + 'h_align': 'center', + 'color': (1, 1, 0.0, 1), + 'shadow': 1.0, + 'vr_depth': -30, + 'flatness': 1.0, + 'position': (0, -60), + 'scale': 0.8, + 'text': txtval, + }, + ) + ) ba.timer(t_sec, self._start_time_bonus_timer) @@ -951,26 +1119,39 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): # player could fill the whole map with them) self._last_wave_end_time = ba.time() + t_sec totalwaves = str(len(self._waves)) if self._waves is not None else '??' - txtval = ba.Lstr(value='${A} ${B}', - subs=[('${A}', ba.Lstr(resource='waveText')), - ('${B}', - str(self._wavenum) + ('' if self._preset in { - Preset.ENDLESS, Preset.ENDLESS_TOURNAMENT - } else f'/{totalwaves}'))]) + txtval = ba.Lstr( + value='${A} ${B}', + subs=[ + ('${A}', ba.Lstr(resource='waveText')), + ( + '${B}', + str(self._wavenum) + + ( + '' + if self._preset + in {Preset.ENDLESS, Preset.ENDLESS_TOURNAMENT} + else f'/{totalwaves}' + ), + ), + ], + ) self._wave_text = ba.NodeActor( - ba.newnode('text', - attrs={ - 'v_attach': 'top', - 'h_attach': 'center', - 'h_align': 'center', - 'vr_depth': -10, - 'color': (1, 1, 1, 1), - 'shadow': 1.0, - 'flatness': 1.0, - 'position': (0, -40), - 'scale': 1.3, - 'text': txtval - })) + ba.newnode( + 'text', + attrs={ + 'v_attach': 'top', + 'h_attach': 'center', + 'h_align': 'center', + 'vr_depth': -10, + 'color': (1, 1, 1, 1), + 'shadow': 1.0, + 'flatness': 1.0, + 'position': (0, -40), + 'scale': 1.3, + 'text': txtval, + }, + ) + ) def _on_bot_spawn(self, path: int, spaz: SpazBot) -> None: @@ -983,21 +1164,25 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): setattr(spaz, 'r_walk_row', path) setattr(spaz, 'r_walk_speed', self._get_bot_speed(spaz_type)) - def add_bot_at_point(self, - point: Point, - spaztype: type[SpazBot], - path: int, - spawn_time: float = 0.1) -> None: + def add_bot_at_point( + self, + point: Point, + spaztype: type[SpazBot], + path: int, + spawn_time: float = 0.1, + ) -> None: """Add the given type bot with the given delay (in seconds).""" # Don't add if the game has ended. if self._game_over: return pos = self.map.defs.points[point.value][:3] - self._bots.spawn_bot(spaztype, - pos=pos, - spawn_time=spawn_time, - on_spawn_call=ba.Call(self._on_bot_spawn, path)) + self._bots.spawn_bot( + spaztype, + pos=pos, + spawn_time=spawn_time, + on_spawn_call=ba.Call(self._on_bot_spawn, path), + ) def _update_time_bonus(self) -> None: self._time_bonus = int(self._time_bonus * 0.91) @@ -1006,16 +1191,19 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): assert self._time_bonus_mult self._time_bonus_text.node.text = ba.Lstr( value='${A}: ${B}', - subs=[('${A}', ba.Lstr(resource='timeBonusText')), - ('${B}', - str(int(self._time_bonus * self._time_bonus_mult)))]) + subs=[ + ('${A}', ba.Lstr(resource='timeBonusText')), + ( + '${B}', + str(int(self._time_bonus * self._time_bonus_mult)), + ), + ], + ) else: self._time_bonus_text = None def _start_updating_waves(self) -> None: - self._wave_update_timer = ba.Timer(2.0, - self._update_waves, - repeat=True) + self._wave_update_timer = ba.Timer(2.0, self._update_waves, repeat=True) def _update_scores(self) -> None: score = self._score @@ -1089,9 +1277,10 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): bot.node.move_left_right = 0 bot.node.run = 0.0 return True - if ((ba.is_point_in_box(pos, boxes['b8']) - and not ba.is_point_in_box(pos, boxes['b9'])) - or pos == (0.0, 0.0, 0.0)): + if ( + ba.is_point_in_box(pos, boxes['b8']) + and not ba.is_point_in_box(pos, boxes['b9']) + ) or pos == (0.0, 0.0, 0.0): # Default to walking right if we're still in the walking area. bot.node.move_left_right = speed @@ -1118,7 +1307,8 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): assert self.initialplayerinfos is not None respawn_time = 2.0 + len(self.initialplayerinfos) * 1.0 player.respawn_timer = ba.Timer( - respawn_time, ba.Call(self.spawn_player_if_exists, player)) + respawn_time, ba.Call(self.spawn_player_if_exists, player) + ) player.respawn_icon = RespawnIcon(player, respawn_time) elif isinstance(msg, SpazBotDiedMessage): @@ -1136,15 +1326,20 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): target = None try: if msg.killerplayer: - self.stats.player_scored(msg.killerplayer, - pts, - target=target, - kill=True, - screenmessage=False, - importance=importance) - ba.playsound(self._dingsound if importance == 1 else - self._dingsoundhigh, - volume=0.6) + self.stats.player_scored( + msg.killerplayer, + pts, + target=target, + kill=True, + screenmessage=False, + importance=importance, + ) + ba.playsound( + self._dingsound + if importance == 1 + else self._dingsoundhigh, + volume=0.6, + ) except Exception: ba.print_exception('Error on SpazBotDiedMessage.') @@ -1161,8 +1356,9 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): def _get_bot_speed(self, bot_type: type[SpazBot]) -> float: speed = self._bot_speed_map.get(bot_type) if speed is None: - raise TypeError('Invalid bot type to _get_bot_speed(): ' + - str(bot_type)) + raise TypeError( + 'Invalid bot type to _get_bot_speed(): ' + str(bot_type) + ) return speed def _set_can_end_wave(self) -> None: diff --git a/dist/ba_data/python/bastd/game/targetpractice.py b/dist/ba_data/python/bastd/game/targetpractice.py index 6437aa5..3788f01 100644 --- a/dist/ba_data/python/bastd/game/targetpractice.py +++ b/dist/ba_data/python/bastd/game/targetpractice.py @@ -44,7 +44,7 @@ class TargetPracticeGame(ba.TeamGameActivity[Player, Team]): available_settings = [ ba.IntSetting('Target Count', min_value=1, default=3), ba.BoolSetting('Enable Impact Bombs', default=True), - ba.BoolSetting('Enable Triple Bombs', default=True) + ba.BoolSetting('Enable Triple Bombs', default=True), ] default_music = ba.MusicType.FORWARD_MARCH @@ -55,8 +55,9 @@ class TargetPracticeGame(ba.TeamGameActivity[Player, Team]): @classmethod def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool: # We support any teams or versus sessions. - return (issubclass(sessiontype, ba.CoopSession) - or issubclass(sessiontype, ba.MultiTeamSession)) + return issubclass(sessiontype, ba.CoopSession) or issubclass( + sessiontype, ba.MultiTeamSession + ) def __init__(self, settings: dict): super().__init__(settings) @@ -86,8 +87,11 @@ class TargetPracticeGame(ba.TeamGameActivity[Player, Team]): def spawn_player(self, player: Player) -> ba.Actor: spawn_center = (0, 3, -5) - pos = (spawn_center[0] + random.uniform(-1.5, 1.5), spawn_center[1], - spawn_center[2] + random.uniform(-1.5, 1.5)) + pos = ( + spawn_center[0] + random.uniform(-1.5, 1.5), + spawn_center[1], + spawn_center[2] + random.uniform(-1.5, 1.5), + ) # Reset their streak. player.streak = 0 @@ -153,7 +157,8 @@ class TargetPracticeGame(ba.TeamGameActivity[Player, Team]): bullseye = any( target.do_hit_at_position(pos, player) - for target in list(self._targets)) + for target in list(self._targets) + ) if bullseye: player.streak += 1 else: @@ -208,39 +213,42 @@ class Target(ba.Actor): # It can be handy to test with this on to make sure the projection # isn't too far off from the actual object. show_in_space = False - loc1 = ba.newnode('locator', - attrs={ - 'shape': 'circle', - 'position': position, - 'color': (0, 1, 0), - 'opacity': 0.5, - 'draw_beauty': show_in_space, - 'additive': True - }) - loc2 = ba.newnode('locator', - attrs={ - 'shape': 'circleOutline', - 'position': position, - 'color': (0, 1, 0), - 'opacity': 0.3, - 'draw_beauty': False, - 'additive': True - }) - loc3 = ba.newnode('locator', - attrs={ - 'shape': 'circleOutline', - 'position': position, - 'color': (0, 1, 0), - 'opacity': 0.1, - 'draw_beauty': False, - 'additive': True - }) + loc1 = ba.newnode( + 'locator', + attrs={ + 'shape': 'circle', + 'position': position, + 'color': (0, 1, 0), + 'opacity': 0.5, + 'draw_beauty': show_in_space, + 'additive': True, + }, + ) + loc2 = ba.newnode( + 'locator', + attrs={ + 'shape': 'circleOutline', + 'position': position, + 'color': (0, 1, 0), + 'opacity': 0.3, + 'draw_beauty': False, + 'additive': True, + }, + ) + loc3 = ba.newnode( + 'locator', + attrs={ + 'shape': 'circleOutline', + 'position': position, + 'color': (0, 1, 0), + 'opacity': 0.1, + 'draw_beauty': False, + 'additive': True, + }, + ) self._nodes = [loc1, loc2, loc3] ba.animate_array(loc1, 'size', 1, {0: [0.0], 0.2: [self._r1 * 2.0]}) - ba.animate_array(loc2, 'size', 1, { - 0.05: [0.0], - 0.25: [self._r2 * 2.0] - }) + ba.animate_array(loc2, 'size', 1, {0.05: [0.0], 0.25: [self._r2 * 2.0]}) ba.animate_array(loc3, 'size', 1, {0.1: [0.0], 0.3: [self._r3 * 2.0]}) ba.playsound(ba.getsound('laserReverse')) @@ -268,7 +276,7 @@ class Target(ba.Actor): if activity.has_ended() or self._hit or not self._nodes: return False - diff = (ba.Vec3(pos) - self._position) + diff = ba.Vec3(pos) - self._position # Disregard Y difference. Our target point probably isn't exactly # on the ground anyway. @@ -284,7 +292,7 @@ class Target(ba.Actor): 0.0: (1.0, 0.0, 0.0), 0.049: (1.0, 0.0, 0.0), 0.05: (1.0, 1.0, 1.0), - 0.1: (0.0, 1.0, 0.0) + 0.1: (0.0, 1.0, 0.0), } cdull = (0.3, 0.3, 0.3) popupcolor: Sequence[float] @@ -301,9 +309,15 @@ class Target(ba.Actor): if streak > 0: ba.playsound( ba.getsound( - 'orchestraHit4' if streak > 3 else - 'orchestraHit3' if streak > 2 else - 'orchestraHit2' if streak > 1 else 'orchestraHit')) + 'orchestraHit4' + if streak > 3 + else 'orchestraHit3' + if streak > 2 + else 'orchestraHit2' + if streak > 1 + else 'orchestraHit' + ) + ) elif dist <= self._r2 + self._rfudge: self._nodes[0].color = cdull self._nodes[2].color = cdull @@ -330,10 +344,12 @@ class Target(ba.Actor): if len(activity.players) > 1: popupcolor = ba.safecolor(player.color, target_intensity=0.75) popupstr += ' ' + player.getname() - PopupText(popupstr, - position=self._position, - color=popupcolor, - scale=popupscale).autoretain() + PopupText( + popupstr, + position=self._position, + color=popupcolor, + scale=popupscale, + ).autoretain() # Give this player's team points and update the score-board. player.team.score += points @@ -343,23 +359,28 @@ class Target(ba.Actor): # Also give this individual player points # (only applies in teams mode). assert activity.stats is not None - activity.stats.player_scored(player, - points, - showpoints=False, - screenmessage=False) + activity.stats.player_scored( + player, points, showpoints=False, screenmessage=False + ) - ba.animate_array(self._nodes[0], 'size', 1, { - 0.8: self._nodes[0].size, - 1.0: [0.0] - }) - ba.animate_array(self._nodes[1], 'size', 1, { - 0.85: self._nodes[1].size, - 1.05: [0.0] - }) - ba.animate_array(self._nodes[2], 'size', 1, { - 0.9: self._nodes[2].size, - 1.1: [0.0] - }) + ba.animate_array( + self._nodes[0], + 'size', + 1, + {0.8: self._nodes[0].size, 1.0: [0.0]}, + ) + ba.animate_array( + self._nodes[1], + 'size', + 1, + {0.85: self._nodes[1].size, 1.05: [0.0]}, + ) + ba.animate_array( + self._nodes[2], + 'size', + 1, + {0.9: self._nodes[2].size, 1.1: [0.0]}, + ) ba.timer(1.1, ba.Call(self.handlemessage, ba.DieMessage())) return bullseye diff --git a/dist/ba_data/python/bastd/game/thelaststand.py b/dist/ba_data/python/bastd/game/thelaststand.py index 3cf84ae..2040e5c 100644 --- a/dist/ba_data/python/bastd/game/thelaststand.py +++ b/dist/ba_data/python/bastd/game/thelaststand.py @@ -13,12 +13,22 @@ from bastd.actor.playerspaz import PlayerSpaz from bastd.actor.bomb import TNTSpawner from bastd.actor.scoreboard import Scoreboard from bastd.actor.powerupbox import PowerupBoxFactory, PowerupBox -from bastd.actor.spazbot import (SpazBotSet, SpazBotDiedMessage, BomberBot, - BomberBotPro, BomberBotProShielded, - BrawlerBot, BrawlerBotPro, - BrawlerBotProShielded, TriggerBot, - TriggerBotPro, TriggerBotProShielded, - ChargerBot, StickyBot, ExplodeyBot) +from bastd.actor.spazbot import ( + SpazBotSet, + SpazBotDiedMessage, + BomberBot, + BomberBotPro, + BomberBotProShielded, + BrawlerBot, + BrawlerBotPro, + BrawlerBotProShielded, + TriggerBot, + TriggerBotPro, + TriggerBotProShielded, + ChargerBot, + StickyBot, + ExplodeyBot, +) if TYPE_CHECKING: from typing import Any, Sequence @@ -28,6 +38,7 @@ if TYPE_CHECKING: @dataclass class SpawnInfo: """Spawning info for a particular bot type.""" + spawnrate: float increase: float dincrease: float @@ -83,25 +94,26 @@ class TheLastStandGame(ba.CoopGameActivity[Player, Team]): # For each bot type: [spawnrate, increase, d_increase] self._bot_spawn_types = { - BomberBot: SpawnInfo(1.00, 0.00, 0.000), - BomberBotPro: SpawnInfo(0.00, 0.05, 0.001), - BomberBotProShielded: SpawnInfo(0.00, 0.02, 0.002), - BrawlerBot: SpawnInfo(1.00, 0.00, 0.000), - BrawlerBotPro: SpawnInfo(0.00, 0.05, 0.001), - BrawlerBotProShielded: SpawnInfo(0.00, 0.02, 0.002), - TriggerBot: SpawnInfo(0.30, 0.00, 0.000), - TriggerBotPro: SpawnInfo(0.00, 0.05, 0.001), - TriggerBotProShielded: SpawnInfo(0.00, 0.02, 0.002), - ChargerBot: SpawnInfo(0.30, 0.05, 0.000), - StickyBot: SpawnInfo(0.10, 0.03, 0.001), - ExplodeyBot: SpawnInfo(0.05, 0.02, 0.002) + BomberBot: SpawnInfo(1.00, 0.00, 0.000), + BomberBotPro: SpawnInfo(0.00, 0.05, 0.001), + BomberBotProShielded: SpawnInfo(0.00, 0.02, 0.002), + BrawlerBot: SpawnInfo(1.00, 0.00, 0.000), + BrawlerBotPro: SpawnInfo(0.00, 0.05, 0.001), + BrawlerBotProShielded: SpawnInfo(0.00, 0.02, 0.002), + TriggerBot: SpawnInfo(0.30, 0.00, 0.000), + TriggerBotPro: SpawnInfo(0.00, 0.05, 0.001), + TriggerBotProShielded: SpawnInfo(0.00, 0.02, 0.002), + ChargerBot: SpawnInfo(0.30, 0.05, 0.000), + StickyBot: SpawnInfo(0.10, 0.03, 0.001), + ExplodeyBot: SpawnInfo(0.05, 0.02, 0.002), } # yapf: disable def on_transition_in(self) -> None: super().on_transition_in() ba.timer(1.3, ba.Call(ba.playsound, self._new_wave_sound)) - self._scoreboard = Scoreboard(label=ba.Lstr(resource='scoreText'), - score_split=0.5) + self._scoreboard = Scoreboard( + label=ba.Lstr(resource='scoreText'), score_split=0.5 + ) def on_begin(self) -> None: super().on_begin() @@ -112,13 +124,16 @@ class TheLastStandGame(ba.CoopGameActivity[Player, Team]): ba.timer(0.001, ba.WeakCall(self._start_bot_updates)) self.setup_low_life_warning_sound() self._update_scores() - self._tntspawner = TNTSpawner(position=self._tntspawnpos, - respawn_time=10.0) + self._tntspawner = TNTSpawner( + position=self._tntspawnpos, respawn_time=10.0 + ) def spawn_player(self, player: Player) -> ba.Actor: - pos = (self._spawn_center[0] + random.uniform(-1.5, 1.5), - self._spawn_center[1], - self._spawn_center[2] + random.uniform(-1.5, 1.5)) + pos = ( + self._spawn_center[0] + random.uniform(-1.5, 1.5), + self._spawn_center[1], + self._spawn_center[2] + random.uniform(-1.5, 1.5), + ) return self.spawn_player_spaz(player, position=pos) def _start_bot_updates(self) -> None: @@ -129,67 +144,86 @@ class TheLastStandGame(ba.CoopGameActivity[Player, Team]): self._update_bots() if len(self.players) > 3: self._update_bots() - self._bot_update_timer = ba.Timer(self._bot_update_interval, - ba.WeakCall(self._update_bots)) + self._bot_update_timer = ba.Timer( + self._bot_update_interval, ba.WeakCall(self._update_bots) + ) - def _drop_powerup(self, - index: int, - poweruptype: str | None = None) -> None: + def _drop_powerup(self, index: int, poweruptype: str | None = None) -> None: if poweruptype is None: - poweruptype = (PowerupBoxFactory.get().get_random_powerup_type( - excludetypes=self._excludepowerups)) - PowerupBox(position=self.map.powerup_spawn_points[index], - poweruptype=poweruptype).autoretain() + poweruptype = PowerupBoxFactory.get().get_random_powerup_type( + excludetypes=self._excludepowerups + ) + PowerupBox( + position=self.map.powerup_spawn_points[index], + poweruptype=poweruptype, + ).autoretain() def _start_powerup_drops(self) -> None: - self._powerup_drop_timer = ba.Timer(3.0, - ba.WeakCall(self._drop_powerups), - repeat=True) + self._powerup_drop_timer = ba.Timer( + 3.0, ba.WeakCall(self._drop_powerups), repeat=True + ) - def _drop_powerups(self, - standard_points: bool = False, - force_first: str | None = None) -> None: + def _drop_powerups( + self, standard_points: bool = False, force_first: str | None = None + ) -> None: """Generic powerup drop.""" from bastd.actor import powerupbox + if standard_points: pts = self.map.powerup_spawn_points for i in range(len(pts)): ba.timer( 1.0 + i * 0.5, - ba.WeakCall(self._drop_powerup, i, - force_first if i == 0 else None)) + ba.WeakCall( + self._drop_powerup, i, force_first if i == 0 else None + ), + ) else: - drop_pt = (self._powerup_center[0] + random.uniform( - -1.0 * self._powerup_spread[0], 1.0 * self._powerup_spread[0]), - self._powerup_center[1], - self._powerup_center[2] + random.uniform( - -self._powerup_spread[1], self._powerup_spread[1])) + drop_pt = ( + self._powerup_center[0] + + random.uniform( + -1.0 * self._powerup_spread[0], + 1.0 * self._powerup_spread[0], + ), + self._powerup_center[1], + self._powerup_center[2] + + random.uniform( + -self._powerup_spread[1], self._powerup_spread[1] + ), + ) # Drop one random one somewhere. powerupbox.PowerupBox( position=drop_pt, poweruptype=PowerupBoxFactory.get().get_random_powerup_type( - excludetypes=self._excludepowerups)).autoretain() + excludetypes=self._excludepowerups + ), + ).autoretain() def do_end(self, outcome: str) -> None: """End the game.""" if outcome == 'defeat': self.fade_to_red() - self.end(delay=2.0, - results={ - 'outcome': outcome, - 'score': self._score, - 'playerinfos': self.initialplayerinfos - }) + self.end( + delay=2.0, + results={ + 'outcome': outcome, + 'score': self._score, + 'playerinfos': self.initialplayerinfos, + }, + ) def _update_bots(self) -> None: assert self._bot_update_interval is not None self._bot_update_interval = max(0.5, self._bot_update_interval * 0.98) - self._bot_update_timer = ba.Timer(self._bot_update_interval, - ba.WeakCall(self._update_bots)) - botspawnpts: list[Sequence[float]] = [[-5.0, 5.5, -4.14], - [0.0, 5.5, -4.14], - [5.0, 5.5, -4.14]] + self._bot_update_timer = ba.Timer( + self._bot_update_interval, ba.WeakCall(self._update_bots) + ) + botspawnpts: list[Sequence[float]] = [ + [-5.0, 5.5, -4.14], + [0.0, 5.5, -4.14], + [5.0, 5.5, -4.14], + ] dists = [0.0, 0.0, 0.0] playerpts: list[Sequence[float]] = [] for player in self.players: @@ -211,8 +245,11 @@ class TheLastStandGame(ba.CoopGameActivity[Player, Team]): else: spawnpt = botspawnpts[2] - spawnpt = (spawnpt[0] + 3.0 * (random.random() - 0.5), spawnpt[1], - 2.0 * (random.random() - 0.5) + spawnpt[2]) + spawnpt = ( + spawnpt[0] + 3.0 * (random.random() - 0.5), + spawnpt[1], + 2.0 * (random.random() - 0.5) + spawnpt[2], + ) # Normalize our bot type total and find a random number within that. total = 0.0 @@ -268,15 +305,18 @@ class TheLastStandGame(ba.CoopGameActivity[Player, Team]): if msg.killerplayer: assert msg.spazbot.node target = msg.spazbot.node.position - self.stats.player_scored(msg.killerplayer, - pts, - target=target, - kill=True, - screenmessage=False, - importance=importance) - ba.playsound(self._dingsound - if importance == 1 else self._dingsoundhigh, - volume=0.6) + self.stats.player_scored( + msg.killerplayer, + pts, + target=target, + kill=True, + screenmessage=False, + importance=importance, + ) + ba.playsound( + self._dingsound if importance == 1 else self._dingsoundhigh, + volume=0.6, + ) # Normally we pull scores from the score-set, but if there's no # player lets be explicit. diff --git a/dist/ba_data/python/bastd/gameutils.py b/dist/ba_data/python/bastd/gameutils.py index 1d297d7..7dbef7c 100644 --- a/dist/ba_data/python/bastd/gameutils.py +++ b/dist/ba_data/python/bastd/gameutils.py @@ -27,8 +27,10 @@ class SharedObjects: def __init__(self) -> None: activity = ba.getactivity() if self._STORENAME in activity.customdata: - raise RuntimeError('Use SharedObjects.get() to fetch the' - ' shared instance for this activity.') + raise RuntimeError( + 'Use SharedObjects.get() to fetch the' + ' shared instance for this activity.' + ) self._object_material: ba.Material | None = None self._player_material: ba.Material | None = None self._pickup_material: ba.Material | None = None @@ -111,7 +113,8 @@ class SharedObjects: if self._death_material is None: mat = self._death_material = ba.Material() mat.add_actions( - ('message', 'their_node', 'at_connect', ba.DieMessage())) + ('message', 'their_node', 'at_connect', ba.DieMessage()) + ) return self._death_material @property diff --git a/dist/ba_data/python/bastd/keyboard/englishkeyboard.py b/dist/ba_data/python/bastd/keyboard/englishkeyboard.py index 47162c5..fdf37a0 100644 --- a/dist/ba_data/python/bastd/keyboard/englishkeyboard.py +++ b/dist/ba_data/python/bastd/keyboard/englishkeyboard.py @@ -43,12 +43,41 @@ def generate_emojis(maxlen: int) -> list[list[str]]: # ba_meta export keyboard class EnglishKeyboard(ba.Keyboard): """Default English keyboard.""" + name = 'English' - chars = [('q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'), - ('a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l'), - ('z', 'x', 'c', 'v', 'b', 'n', 'm')] - nums = ('1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '/', ':', - ';', '(', ')', '$', '&', '@', '"', '.', ',', '?', '!', '\'', '_') + chars = [ + ('q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'), + ('a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l'), + ('z', 'x', 'c', 'v', 'b', 'n', 'm'), + ] + nums = ( + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + '0', + '-', + '/', + ':', + ';', + '(', + ')', + '$', + '&', + '@', + '"', + '.', + ',', + '?', + '!', + '\'', + '_', + ) pages: dict[str, tuple[str, ...]] = { f'emoji{i}': tuple(page) for i, page in enumerate(generate_emojis(len(nums))) diff --git a/dist/ba_data/python/bastd/mainmenu.py b/dist/ba_data/python/bastd/mainmenu.py index ed695c0..2c8ce4c 100644 --- a/dist/ba_data/python/bastd/mainmenu.py +++ b/dist/ba_data/python/bastd/mainmenu.py @@ -1,6 +1,7 @@ # Released under the MIT License. See LICENSE for details. # """Session and Activity for displaying the main menu bg.""" +# pylint: disable=too-many-lines from __future__ import annotations @@ -44,42 +45,51 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]): vr_mode = ba.app.vr_mode if not ba.app.toolbar_test: - color = ((1.0, 1.0, 1.0, 1.0) if vr_mode else (0.5, 0.6, 0.5, 0.6)) + color = (1.0, 1.0, 1.0, 1.0) if vr_mode else (0.5, 0.6, 0.5, 0.6) # FIXME: Need a node attr for vr-specific-scale. - scale = (0.9 if - (app.ui.uiscale is ba.UIScale.SMALL or vr_mode) else 0.7) + scale = ( + 0.9 if (app.ui.uiscale is ba.UIScale.SMALL or vr_mode) else 0.7 + ) self.my_name = ba.NodeActor( - ba.newnode('text', - attrs={ - 'v_attach': 'bottom', - 'h_align': 'center', - 'color': color, - 'flatness': 1.0, - 'shadow': 1.0 if vr_mode else 0.5, - 'scale': scale, - 'position': (0, 10), - 'vr_depth': -10, - 'text': '\xa9 2011-2022 Eric Froemling' - })) + ba.newnode( + 'text', + attrs={ + 'v_attach': 'bottom', + 'h_align': 'center', + 'color': color, + 'flatness': 1.0, + 'shadow': 1.0 if vr_mode else 0.5, + 'scale': scale, + 'position': (0, 10), + 'vr_depth': -10, + 'text': '\xa9 2011-2022 Eric Froemling', + }, + ) + ) # Throw up some text that only clients can see so they know that the # host is navigating menus while they're just staring at an # empty-ish screen. - tval = ba.Lstr(resource='hostIsNavigatingMenusText', - subs=[('${HOST}', - ba.internal.get_v1_account_display_string())]) + tval = ba.Lstr( + resource='hostIsNavigatingMenusText', + subs=[('${HOST}', ba.internal.get_v1_account_display_string())], + ) self._host_is_navigating_text = ba.NodeActor( - ba.newnode('text', - attrs={ - 'text': tval, - 'client_only': True, - 'position': (0, -200), - 'flatness': 1.0, - 'h_align': 'center' - })) + ba.newnode( + 'text', + attrs={ + 'text': tval, + 'client_only': True, + 'position': (0, -200), + 'flatness': 1.0, + 'h_align': 'center', + }, + ) + ) if not ba.app.main_menu_did_initial_transition and hasattr( - self, 'my_name'): + self, 'my_name' + ): assert self.my_name.node ba.animate(self.my_name.node, 'opacity', {2.3: 0, 3.0: 1.0}) @@ -97,18 +107,22 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]): if not ba.app.toolbar_test: if app.debug_build or app.test_build or force_show_build_number: if app.debug_build: - text = ba.Lstr(value='${V} (${B}) (${D})', - subs=[ - ('${V}', app.version), - ('${B}', str(app.build_number)), - ('${D}', ba.Lstr(resource='debugText')), - ]) + text = ba.Lstr( + value='${V} (${B}) (${D})', + subs=[ + ('${V}', app.version), + ('${B}', str(app.build_number)), + ('${D}', ba.Lstr(resource='debugText')), + ], + ) else: - text = ba.Lstr(value='${V} (${B})', - subs=[ - ('${V}', app.version), - ('${B}', str(app.build_number)), - ]) + text = ba.Lstr( + value='${V} (${B})', + subs=[ + ('${V}', app.version), + ('${B}', str(app.build_number)), + ], + ) else: text = ba.Lstr(value='${V}', subs=[('${V}', app.version)]) scale = 0.9 if (uiscale is ba.UIScale.SMALL or vr_mode) else 0.7 @@ -126,8 +140,10 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]): 'color': color, 'scale': scale, 'position': (-260, 10) if vr_mode else (-10, 10), - 'text': text - })) + 'text': text, + }, + ) + ) if not ba.app.main_menu_did_initial_transition: assert self.version.node ba.animate(self.version.node, 'opacity', {2.3: 0, 3.0: 1.0}) @@ -135,39 +151,45 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]): # Show the iircade logo on our iircade build. if app.iircade_mode: img = ba.NodeActor( - ba.newnode('image', - attrs={ - 'texture': ba.gettexture('iircadeLogo'), - 'attach': 'center', - 'scale': (250, 250), - 'position': (0, 0), - 'tilt_translate': 0.21, - 'absolute_scale': True - })).autoretain() + ba.newnode( + 'image', + attrs={ + 'texture': ba.gettexture('iircadeLogo'), + 'attach': 'center', + 'scale': (250, 250), + 'position': (0, 0), + 'tilt_translate': 0.21, + 'absolute_scale': True, + }, + ) + ).autoretain() imgdelay = 0.0 if app.main_menu_did_initial_transition else 1.0 - ba.animate(img.node, 'opacity', { - imgdelay + 1.5: 0.0, - imgdelay + 2.5: 1.0 - }) + ba.animate( + img.node, 'opacity', {imgdelay + 1.5: 0.0, imgdelay + 2.5: 1.0} + ) # Throw in test build info. self.beta_info = self.beta_info_2 = None if app.test_build and not (app.demo_mode or app.arcade_mode): - pos = ((230, 125) if (app.demo_mode or app.arcade_mode) else - (230, 35)) + pos = ( + (230, 125) if (app.demo_mode or app.arcade_mode) else (230, 35) + ) self.beta_info = ba.NodeActor( - ba.newnode('text', - attrs={ - 'v_attach': 'center', - 'h_align': 'center', - 'color': (1, 1, 1, 1), - 'shadow': 0.5, - 'flatness': 0.5, - 'scale': 1, - 'vr_depth': -60, - 'position': pos, - 'text': ba.Lstr(resource='testBuildText') - })) + ba.newnode( + 'text', + attrs={ + 'v_attach': 'center', + 'h_align': 'center', + 'color': (1, 1, 1, 1), + 'shadow': 0.5, + 'flatness': 0.5, + 'scale': 1, + 'vr_depth': -60, + 'position': pos, + 'text': ba.Lstr(resource='testBuildText'), + }, + ) + ) if not ba.app.main_menu_did_initial_transition: assert self.beta_info.node ba.animate(self.beta_info.node, 'opacity', {1.3: 0, 1.8: 1.0}) @@ -194,56 +216,74 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]): gnode.vignette_inner = (0.99, 0.98, 0.98) self.bottom = ba.NodeActor( - ba.newnode('terrain', - attrs={ - 'model': bottom_model, - 'lighting': False, - 'reflection': 'soft', - 'reflection_scale': [0.45], - 'color_texture': color_texture - })) + ba.newnode( + 'terrain', + attrs={ + 'model': bottom_model, + 'lighting': False, + 'reflection': 'soft', + 'reflection_scale': [0.45], + 'color_texture': color_texture, + }, + ) + ) self.vr_bottom_fill = ba.NodeActor( - ba.newnode('terrain', - attrs={ - 'model': vr_bottom_fill_model, - 'lighting': False, - 'vr_only': True, - 'color_texture': color_texture - })) + ba.newnode( + 'terrain', + attrs={ + 'model': vr_bottom_fill_model, + 'lighting': False, + 'vr_only': True, + 'color_texture': color_texture, + }, + ) + ) self.vr_top_fill = ba.NodeActor( - ba.newnode('terrain', - attrs={ - 'model': vr_top_fill_model, - 'vr_only': True, - 'lighting': False, - 'color_texture': bgtex - })) + ba.newnode( + 'terrain', + attrs={ + 'model': vr_top_fill_model, + 'vr_only': True, + 'lighting': False, + 'color_texture': bgtex, + }, + ) + ) self.terrain = ba.NodeActor( - ba.newnode('terrain', - attrs={ - 'model': model, - 'color_texture': color_texture, - 'reflection': 'soft', - 'reflection_scale': [0.3] - })) + ba.newnode( + 'terrain', + attrs={ + 'model': model, + 'color_texture': color_texture, + 'reflection': 'soft', + 'reflection_scale': [0.3], + }, + ) + ) self.trees = ba.NodeActor( - ba.newnode('terrain', - attrs={ - 'model': trees_model, - 'lighting': False, - 'reflection': 'char', - 'reflection_scale': [0.1], - 'color_texture': trees_texture - })) + ba.newnode( + 'terrain', + attrs={ + 'model': trees_model, + 'lighting': False, + 'reflection': 'char', + 'reflection_scale': [0.1], + 'color_texture': trees_texture, + }, + ) + ) self.bgterrain = ba.NodeActor( - ba.newnode('terrain', - attrs={ - 'model': bgmodel, - 'color': (0.92, 0.91, 0.9), - 'lighting': False, - 'background': True, - 'color_texture': bgtex - })) + ba.newnode( + 'terrain', + attrs={ + 'model': bgmodel, + 'color': (0.92, 0.91, 0.9), + 'lighting': False, + 'background': True, + 'color_texture': bgtex, + }, + ) + ) self._ts = 0.86 @@ -270,7 +310,8 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]): # If we're signed in, fetch news immediately. # Otherwise wait until we are signed in. self._fetch_timer: ba.Timer | None = ba.Timer( - 1.0, ba.WeakCall(self._try_fetching_news), repeat=True) + 1.0, ba.WeakCall(self._try_fetching_news), repeat=True + ) self._try_fetching_news() # We now want to wait until we're signed in before fetching news. @@ -304,51 +345,60 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]): val = self._phrases.pop() if val == '__ACH__': vrmode = app.vr_mode - Text(ba.Lstr(resource='nextAchievementsText'), - color=((1, 1, 1, 1) if vrmode else - (0.95, 0.9, 1, 0.4)), - host_only=True, - maxwidth=200, - position=(-300, -35), - h_align=Text.HAlign.RIGHT, - transition=Text.Transition.FADE_IN, - scale=0.9 if vrmode else 0.7, - flatness=1.0 if vrmode else 0.6, - shadow=1.0 if vrmode else 0.5, - h_attach=Text.HAttach.CENTER, - v_attach=Text.VAttach.TOP, - transition_delay=1.0, - transition_out_delay=self._message_duration - ).autoretain() + Text( + ba.Lstr(resource='nextAchievementsText'), + color=( + (1, 1, 1, 1) + if vrmode + else (0.95, 0.9, 1, 0.4) + ), + host_only=True, + maxwidth=200, + position=(-300, -35), + h_align=Text.HAlign.RIGHT, + transition=Text.Transition.FADE_IN, + scale=0.9 if vrmode else 0.7, + flatness=1.0 if vrmode else 0.6, + shadow=1.0 if vrmode else 0.5, + h_attach=Text.HAttach.CENTER, + v_attach=Text.VAttach.TOP, + transition_delay=1.0, + transition_out_delay=self._message_duration, + ).autoretain() achs = [ - a for a in app.ach.achievements + a + for a in app.ach.achievements if not a.complete ] if achs: ach = achs.pop( - random.randrange(min(4, len(achs)))) + random.randrange(min(4, len(achs))) + ) ach.create_display( -180, -35, 1.0, outdelay=self._message_duration, - style='news') + style='news', + ) if achs: ach = achs.pop( - random.randrange(min(8, len(achs)))) + random.randrange(min(8, len(achs))) + ) ach.create_display( 180, -35, 1.25, outdelay=self._message_duration, - style='news') + style='news', + ) else: spc = self._message_spacing keys = { spc: 0.0, spc + 1.0: 1.0, spc + self._message_duration - 1.0: 1.0, - spc + self._message_duration: 0.0 + spc + self._message_duration: 0.0, } assert self._text.node ba.animate(self._text.node, 'opacity', keys) @@ -370,34 +420,47 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]): # Show upcoming achievements in non-vr versions # (currently too hard to read in vr). self._used_phrases = ( - ['__ACH__'] if not ba.app.vr_mode else - []) + [s for s in news.split('
\n') if s != ''] + ['__ACH__'] if not ba.app.vr_mode else [] + ) + [s for s in news.split('
\n') if s != ''] self._phrase_change_timer = ba.Timer( (self._message_duration + self._message_spacing), ba.WeakCall(self._change_phrase), - repeat=True) + repeat=True, + ) - scl = 1.2 if (ba.app.ui.uiscale is ba.UIScale.SMALL - or ba.app.vr_mode) else 0.8 + scl = ( + 1.2 + if ( + ba.app.ui.uiscale is ba.UIScale.SMALL + or ba.app.vr_mode + ) + else 0.8 + ) - color2 = ((1, 1, 1, 1) if ba.app.vr_mode else - (0.7, 0.65, 0.75, 1.0)) - shadow = (1.0 if ba.app.vr_mode else 0.4) + color2 = ( + (1, 1, 1, 1) + if ba.app.vr_mode + else (0.7, 0.65, 0.75, 1.0) + ) + shadow = 1.0 if ba.app.vr_mode else 0.4 self._text = ba.NodeActor( - ba.newnode('text', - attrs={ - 'v_attach': 'top', - 'h_attach': 'center', - 'h_align': 'center', - 'vr_depth': -20, - 'shadow': shadow, - 'flatness': 0.8, - 'v_align': 'top', - 'color': color2, - 'scale': scl, - 'maxwidth': 900.0 / scl, - 'position': (0, -10) - })) + ba.newnode( + 'text', + attrs={ + 'v_attach': 'top', + 'h_attach': 'center', + 'h_align': 'center', + 'vr_depth': -20, + 'shadow': shadow, + 'flatness': 0.8, + 'v_align': 'top', + 'color': color2, + 'scale': scl, + 'maxwidth': 900.0 / scl, + 'position': (0, -10), + }, + ) + ) self._change_phrase() if not (app.demo_mode or app.arcade_mode) and not app.toolbar_test: @@ -406,6 +469,7 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]): # Bring up the last place we were, or start at the main menu otherwise. with ba.Context('ui'): from bastd.ui import specialoffer + if bool(False): uicontroller = ba.app.ui.controller assert uicontroller is not None @@ -418,52 +482,70 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]): if ba.app.demo_mode or ba.app.arcade_mode: # pylint: disable=cyclic-import from bastd.ui.kiosk import KioskWindow + ba.app.ui.set_main_menu_window( - KioskWindow().get_root_widget()) + KioskWindow().get_root_widget() + ) # ..or in normal cases go back to the main menu else: if main_menu_location == 'Gather': # pylint: disable=cyclic-import from bastd.ui.gather import GatherWindow + ba.app.ui.set_main_menu_window( - GatherWindow(transition=None).get_root_widget()) + GatherWindow(transition=None).get_root_widget() + ) elif main_menu_location == 'Watch': # pylint: disable=cyclic-import from bastd.ui.watch import WatchWindow + ba.app.ui.set_main_menu_window( - WatchWindow(transition=None).get_root_widget()) + WatchWindow(transition=None).get_root_widget() + ) elif main_menu_location == 'Team Game Select': # pylint: disable=cyclic-import from bastd.ui.playlist.browser import ( - PlaylistBrowserWindow) + PlaylistBrowserWindow, + ) + ba.app.ui.set_main_menu_window( PlaylistBrowserWindow( - sessiontype=ba.DualTeamSession, - transition=None).get_root_widget()) + sessiontype=ba.DualTeamSession, transition=None + ).get_root_widget() + ) elif main_menu_location == 'Free-for-All Game Select': # pylint: disable=cyclic-import from bastd.ui.playlist.browser import ( - PlaylistBrowserWindow) + PlaylistBrowserWindow, + ) + ba.app.ui.set_main_menu_window( PlaylistBrowserWindow( sessiontype=ba.FreeForAllSession, - transition=None).get_root_widget()) + transition=None, + ).get_root_widget() + ) elif main_menu_location == 'Coop Select': # pylint: disable=cyclic-import from bastd.ui.coop.browser import CoopBrowserWindow + ba.app.ui.set_main_menu_window( - CoopBrowserWindow( - transition=None).get_root_widget()) + CoopBrowserWindow(transition=None).get_root_widget() + ) elif main_menu_location == 'Benchmarks & Stress Tests': # pylint: disable=cyclic-import from bastd.ui.debug import DebugWindow + ba.app.ui.set_main_menu_window( - DebugWindow(transition=None).get_root_widget()) + DebugWindow(transition=None).get_root_widget() + ) else: # pylint: disable=cyclic-import from bastd.ui.mainmenu import MainMenuWindow + ba.app.ui.set_main_menu_window( - MainMenuWindow(transition=None).get_root_widget()) + MainMenuWindow(transition=None).get_root_widget() + ) # attempt to show any pending offers immediately. # If that doesn't work, try again in a few seconds @@ -475,9 +557,11 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]): def try_again() -> None: if not specialoffer.show_offer(): # Try one last time.. - ba.timer(2.0, - specialoffer.show_offer, - timetype=ba.TimeType.REAL) + ba.timer( + 2.0, + specialoffer.show_offer, + timetype=ba.TimeType.REAL, + ) ba.timer(2.0, try_again, timetype=ba.TimeType.REAL) ba.app.main_menu_did_initial_transition = True @@ -491,13 +575,16 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]): if custom_texture != self._custom_logo_tex_name: self._custom_logo_tex_name = custom_texture self._logo_node.texture = ba.gettexture( - custom_texture if custom_texture is not None else 'logo') - self._logo_node.model_opaque = (None - if custom_texture is not None - else ba.getmodel('logo')) + custom_texture if custom_texture is not None else 'logo' + ) + self._logo_node.model_opaque = ( + None if custom_texture is not None else ba.getmodel('logo') + ) self._logo_node.model_transparent = ( - None if custom_texture is not None else - ba.getmodel('logoTransparent')) + None + if custom_texture is not None + else ba.getmodel('logoTransparent') + ) # If language has changed, recreate our logo text/graphics. lang = app.lang.language @@ -524,54 +611,66 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]): x = base_x - 20.0 spacing = 85.0 * base_scale y_extra = 0.0 if (app.demo_mode or app.arcade_mode) else 0.0 - self._make_logo(x - 110 + 50, - 113 + y + 1.2 * y_extra, - 0.34 * base_scale, - delay=base_delay + 0.1, - custom_texture='chTitleChar1', - jitter_scale=2.0, - vr_depth_offset=-30) + self._make_logo( + x - 110 + 50, + 113 + y + 1.2 * y_extra, + 0.34 * base_scale, + delay=base_delay + 0.1, + custom_texture='chTitleChar1', + jitter_scale=2.0, + vr_depth_offset=-30, + ) x += spacing delay += delay_inc - self._make_logo(x - 10 + 50, - 110 + y + 1.2 * y_extra, - 0.31 * base_scale, - delay=base_delay + 0.15, - custom_texture='chTitleChar2', - jitter_scale=2.0, - vr_depth_offset=-30) + self._make_logo( + x - 10 + 50, + 110 + y + 1.2 * y_extra, + 0.31 * base_scale, + delay=base_delay + 0.15, + custom_texture='chTitleChar2', + jitter_scale=2.0, + vr_depth_offset=-30, + ) x += 2.0 * spacing delay += delay_inc - self._make_logo(x + 180 - 140, - 110 + y + 1.2 * y_extra, - 0.3 * base_scale, - delay=base_delay + 0.25, - custom_texture='chTitleChar3', - jitter_scale=2.0, - vr_depth_offset=-30) + self._make_logo( + x + 180 - 140, + 110 + y + 1.2 * y_extra, + 0.3 * base_scale, + delay=base_delay + 0.25, + custom_texture='chTitleChar3', + jitter_scale=2.0, + vr_depth_offset=-30, + ) x += spacing delay += delay_inc - self._make_logo(x + 241 - 120, - 110 + y + 1.2 * y_extra, - 0.31 * base_scale, - delay=base_delay + 0.3, - custom_texture='chTitleChar4', - jitter_scale=2.0, - vr_depth_offset=-30) + self._make_logo( + x + 241 - 120, + 110 + y + 1.2 * y_extra, + 0.31 * base_scale, + delay=base_delay + 0.3, + custom_texture='chTitleChar4', + jitter_scale=2.0, + vr_depth_offset=-30, + ) x += spacing delay += delay_inc - self._make_logo(x + 300 - 90, - 105 + y + 1.2 * y_extra, - 0.34 * base_scale, - delay=base_delay + 0.35, - custom_texture='chTitleChar5', - jitter_scale=2.0, - vr_depth_offset=-30) - self._make_logo(base_x + 155, - 146 + y + 1.2 * y_extra, - 0.28 * base_scale, - delay=base_delay + 0.2, - rotate=-7) + self._make_logo( + x + 300 - 90, + 105 + y + 1.2 * y_extra, + 0.34 * base_scale, + delay=base_delay + 0.35, + custom_texture='chTitleChar5', + jitter_scale=2.0, + vr_depth_offset=-30, + ) + self._make_logo( + base_x + 155, + 146 + y + 1.2 * y_extra, + 0.28 * base_scale, + delay=base_delay + 0.2, + rotate=-7, + ) else: base_x = -170 x = base_x - 20 @@ -582,118 +681,144 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]): for shadow in (True, False): x = xv1 delay = delay1 - self._make_word('B', - x - 50, - y - 23 + 0.8 * y_extra, - scale=1.3 * base_scale, - delay=delay, - vr_depth_offset=3, - shadow=shadow) + self._make_word( + 'B', + x - 50, + y - 23 + 0.8 * y_extra, + scale=1.3 * base_scale, + delay=delay, + vr_depth_offset=3, + shadow=shadow, + ) x += spacing delay += delay_inc - self._make_word('m', - x, - y + y_extra, - delay=delay, - scale=base_scale, - shadow=shadow) + self._make_word( + 'm', + x, + y + y_extra, + delay=delay, + scale=base_scale, + shadow=shadow, + ) x += spacing * 1.25 delay += delay_inc - self._make_word('b', - x, - y + y_extra - 10, - delay=delay, - scale=1.1 * base_scale, - vr_depth_offset=5, - shadow=shadow) + self._make_word( + 'b', + x, + y + y_extra - 10, + delay=delay, + scale=1.1 * base_scale, + vr_depth_offset=5, + shadow=shadow, + ) x += spacing * 0.85 delay += delay_inc - self._make_word('S', - x, - y - 25 + 0.8 * y_extra, - scale=1.35 * base_scale, - delay=delay, - vr_depth_offset=14, - shadow=shadow) + self._make_word( + 'S', + x, + y - 25 + 0.8 * y_extra, + scale=1.35 * base_scale, + delay=delay, + vr_depth_offset=14, + shadow=shadow, + ) x += spacing delay += delay_inc - self._make_word('q', - x, - y + y_extra, - delay=delay, - scale=base_scale, - shadow=shadow) + self._make_word( + 'q', + x, + y + y_extra, + delay=delay, + scale=base_scale, + shadow=shadow, + ) x += spacing * 0.9 delay += delay_inc - self._make_word('u', - x, - y + y_extra, - delay=delay, - scale=base_scale, - vr_depth_offset=7, - shadow=shadow) + self._make_word( + 'u', + x, + y + y_extra, + delay=delay, + scale=base_scale, + vr_depth_offset=7, + shadow=shadow, + ) x += spacing * 0.9 delay += delay_inc - self._make_word('a', - x, - y + y_extra, - delay=delay, - scale=base_scale, - shadow=shadow) + self._make_word( + 'a', + x, + y + y_extra, + delay=delay, + scale=base_scale, + shadow=shadow, + ) x += spacing * 0.64 delay += delay_inc - self._make_word('d', - x, - y + y_extra - 10, - delay=delay, - scale=1.1 * base_scale, - vr_depth_offset=6, - shadow=shadow) - self._make_logo(base_x - 28, - 125 + y + 1.2 * y_extra, - 0.32 * base_scale, - delay=base_delay) + self._make_word( + 'd', + x, + y + y_extra - 10, + delay=delay, + scale=1.1 * base_scale, + vr_depth_offset=6, + shadow=shadow, + ) + self._make_logo( + base_x - 28, + 125 + y + 1.2 * y_extra, + 0.32 * base_scale, + delay=base_delay, + ) - def _make_word(self, - word: str, - x: float, - y: float, - scale: float = 1.0, - delay: float = 0.0, - vr_depth_offset: float = 0.0, - shadow: bool = False) -> None: + def _make_word( + self, + word: str, + x: float, + y: float, + scale: float = 1.0, + delay: float = 0.0, + vr_depth_offset: float = 0.0, + shadow: bool = False, + ) -> None: if shadow: word_obj = ba.NodeActor( - ba.newnode('text', - attrs={ - 'position': (x, y), - 'big': True, - 'color': (0.0, 0.0, 0.2, 0.08), - 'tilt_translate': 0.09, - 'opacity_scales_shadow': False, - 'shadow': 0.2, - 'vr_depth': -130, - 'v_align': 'center', - 'project_scale': 0.97 * scale, - 'scale': 1.0, - 'text': word - })) + ba.newnode( + 'text', + attrs={ + 'position': (x, y), + 'big': True, + 'color': (0.0, 0.0, 0.2, 0.08), + 'tilt_translate': 0.09, + 'opacity_scales_shadow': False, + 'shadow': 0.2, + 'vr_depth': -130, + 'v_align': 'center', + 'project_scale': 0.97 * scale, + 'scale': 1.0, + 'text': word, + }, + ) + ) self._word_actors.append(word_obj) else: word_obj = ba.NodeActor( - ba.newnode('text', - attrs={ - 'position': (x, y), - 'big': True, - 'color': (1.2, 1.15, 1.15, 1.0), - 'tilt_translate': 0.11, - 'shadow': 0.2, - 'vr_depth': -40 + vr_depth_offset, - 'v_align': 'center', - 'project_scale': scale, - 'scale': 1.0, - 'text': word - })) + ba.newnode( + 'text', + attrs={ + 'position': (x, y), + 'big': True, + 'color': (1.2, 1.15, 1.15, 1.0), + 'tilt_translate': 0.11, + 'shadow': 0.2, + 'vr_depth': -40 + vr_depth_offset, + 'v_align': 'center', + 'project_scale': scale, + 'scale': 1.0, + 'text': word, + }, + ) + ) self._word_actors.append(word_obj) # Add a bit of stop-motion-y jitter to the logo @@ -703,15 +828,15 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]): cmb: ba.Node | None cmb2: ba.Node | None if not shadow: - cmb = ba.newnode('combine', - owner=word_obj.node, - attrs={'size': 2}) + cmb = ba.newnode( + 'combine', owner=word_obj.node, attrs={'size': 2} + ) else: cmb = None if shadow: - cmb2 = ba.newnode('combine', - owner=word_obj.node, - attrs={'size': 2}) + cmb2 = ba.newnode( + 'combine', owner=word_obj.node, attrs={'size': 2} + ) else: cmb2 = None if not shadow: @@ -749,18 +874,18 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]): if not shadow: assert word_obj.node - ba.animate(word_obj.node, 'project_scale', { - delay: 0.0, - delay + 0.1: scale * 1.1, - delay + 0.2: scale - }) + ba.animate( + word_obj.node, + 'project_scale', + {delay: 0.0, delay + 0.1: scale * 1.1, delay + 0.2: scale}, + ) else: assert word_obj.node - ba.animate(word_obj.node, 'project_scale', { - delay: 0.0, - delay + 0.1: scale * 1.1, - delay + 0.2: scale - }) + ba.animate( + word_obj.node, + 'project_scale', + {delay: 0.0, delay + 0.1: scale * 1.1, delay + 0.2: scale}, + ) def _get_custom_logo_tex_name(self) -> str | None: if ba.internal.get_v1_account_misc_read_val('easter', False): @@ -768,37 +893,46 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]): return None # Pop the logo and menu in. - def _make_logo(self, - x: float, - y: float, - scale: float, - delay: float, - custom_texture: str | None = None, - jitter_scale: float = 1.0, - rotate: float = 0.0, - vr_depth_offset: float = 0.0) -> None: + def _make_logo( + self, + x: float, + y: float, + scale: float, + delay: float, + custom_texture: str | None = None, + jitter_scale: float = 1.0, + rotate: float = 0.0, + vr_depth_offset: float = 0.0, + ) -> None: # Temp easter goodness. if custom_texture is None: custom_texture = self._get_custom_logo_tex_name() self._custom_logo_tex_name = custom_texture ltex = ba.gettexture( - custom_texture if custom_texture is not None else 'logo') - mopaque = (None if custom_texture is not None else ba.getmodel('logo')) - mtrans = (None if custom_texture is not None else - ba.getmodel('logoTransparent')) + custom_texture if custom_texture is not None else 'logo' + ) + mopaque = None if custom_texture is not None else ba.getmodel('logo') + mtrans = ( + None + if custom_texture is not None + else ba.getmodel('logoTransparent') + ) logo = ba.NodeActor( - ba.newnode('image', - attrs={ - 'texture': ltex, - 'model_opaque': mopaque, - 'model_transparent': mtrans, - 'vr_depth': -10 + vr_depth_offset, - 'rotate': rotate, - 'attach': 'center', - 'tilt_translate': 0.21, - 'absolute_scale': True - })) + ba.newnode( + 'image', + attrs={ + 'texture': ltex, + 'model_opaque': mopaque, + 'model_transparent': mtrans, + 'vr_depth': -10 + vr_depth_offset, + 'rotate': rotate, + 'attach': 'center', + 'tilt_translate': 0.21, + 'absolute_scale': True, + }, + ) + ) self._logo_node = logo.node self._word_actors.append(logo) @@ -820,8 +954,9 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]): keys = {} time_v = 0.0 for _i in range(10): - keys[time_v * self._ts] = y + (random.random() - - 0.5) * 0.7 * jitter_scale + keys[time_v * self._ts] = ( + y + (random.random() - 0.5) * 0.7 * jitter_scale + ) time_v += random.random() * 0.1 ba.animate(cmb, 'input1', keys, loop=True) else: @@ -832,7 +967,7 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]): keys = { delay: 0.0, delay + 0.1: 700.0 * scale, - delay + 0.2: 600.0 * scale + delay + 0.2: 600.0 * scale, } ba.animate(cmb, 'input0', keys) ba.animate(cmb, 'input1', keys) @@ -855,21 +990,31 @@ def _preload1() -> None: Helps avoid hitches later on. """ for mname in [ - 'plasticEyesTransparent', 'playerLineup1Transparent', - 'playerLineup2Transparent', 'playerLineup3Transparent', - 'playerLineup4Transparent', 'angryComputerTransparent', - 'scrollWidgetShort', 'windowBGBlotch' + 'plasticEyesTransparent', + 'playerLineup1Transparent', + 'playerLineup2Transparent', + 'playerLineup3Transparent', + 'playerLineup4Transparent', + 'angryComputerTransparent', + 'scrollWidgetShort', + 'windowBGBlotch', ]: ba.getmodel(mname) for tname in ['playerLineup', 'lock']: ba.gettexture(tname) for tex in [ - 'iconRunaround', 'iconOnslaught', 'medalComplete', 'medalBronze', - 'medalSilver', 'medalGold', 'characterIconMask' + 'iconRunaround', + 'iconOnslaught', + 'medalComplete', + 'medalBronze', + 'medalSilver', + 'medalGold', + 'characterIconMask', ]: ba.gettexture(tex) ba.gettexture('bg') from bastd.actor.powerupbox import PowerupBoxFactory + PowerupBoxFactory.get() ba.timer(0.1, _preload2) @@ -881,28 +1026,44 @@ def _preload2() -> None: for mname in ['powerup', 'powerupSimple']: ba.getmodel(mname) for tname in [ - 'powerupBomb', 'powerupSpeed', 'powerupPunch', 'powerupIceBombs', - 'powerupStickyBombs', 'powerupShield', 'powerupImpactBombs', - 'powerupHealth' + 'powerupBomb', + 'powerupSpeed', + 'powerupPunch', + 'powerupIceBombs', + 'powerupStickyBombs', + 'powerupShield', + 'powerupImpactBombs', + 'powerupHealth', ]: ba.gettexture(tname) for sname in [ - 'powerup01', 'boxDrop', 'boxingBell', 'scoreHit01', 'scoreHit02', - 'dripity', 'spawn', 'gong' + 'powerup01', + 'boxDrop', + 'boxingBell', + 'scoreHit01', + 'scoreHit02', + 'dripity', + 'spawn', + 'gong', ]: ba.getsound(sname) from bastd.actor.bomb import BombFactory + BombFactory.get() ba.timer(0.1, _preload3) def _preload3() -> None: from bastd.actor.spazfactory import SpazFactory + for mname in ['bomb', 'bombSticky', 'impactBomb']: ba.getmodel(mname) for tname in [ - 'bombColor', 'bombColorIce', 'bombStickyColor', 'impactBombColor', - 'impactBombColorLit' + 'bombColor', + 'bombColorIce', + 'bombStickyColor', + 'impactBombColor', + 'impactBombColorLit', ]: ba.gettexture(tname) for sname in ['freeze', 'fuse01', 'activateBeep', 'warnBeep']: @@ -919,6 +1080,7 @@ def _preload4() -> None: for sname in ['metalHit', 'metalSkid', 'refWhistle', 'achievement']: ba.getsound(sname) from bastd.actor.flag import FlagFactory + FlagFactory.get() diff --git a/dist/ba_data/python/bastd/mapdata/big_g.py b/dist/ba_data/python/bastd/mapdata/big_g.py index 207e93e..8bc8272 100644 --- a/dist/ba_data/python/bastd/mapdata/big_g.py +++ b/dist/ba_data/python/bastd/mapdata/big_g.py @@ -6,26 +6,46 @@ points = {} # noinspection PyDictCreation boxes = {} -boxes['area_of_interest_bounds'] = (-0.4011866709, 2.331310176, - -0.5426286416) + (0.0, 0.0, 0.0) + ( - 19.11746262, 10.19675564, 23.50119277) -points['ffa_spawn1'] = (3.140826121, 1.16512015, - 6.172121491) + (4.739204545, 1.0, 1.028864849) +boxes['area_of_interest_bounds'] = ( + (-0.4011866709, 2.331310176, -0.5426286416) + + (0.0, 0.0, 0.0) + + (19.11746262, 10.19675564, 23.50119277) +) +points['ffa_spawn1'] = (3.140826121, 1.16512015, 6.172121491) + ( + 4.739204545, + 1.0, + 1.028864849, +) points['ffa_spawn2'] = (5.416289073, 1.180022599, -0.1696495695) + ( - 2.945888237, 0.621599724, 0.4969830881) + 2.945888237, + 0.621599724, + 0.4969830881, +) points['ffa_spawn3'] = (-0.3692088357, 2.88984723, -6.909741615) + ( - 7.575371952, 0.621599724, 0.4969830881) + 7.575371952, + 0.621599724, + 0.4969830881, +) points['ffa_spawn4'] = (-2.391932409, 1.123690253, -3.417262271) + ( - 2.933065031, 0.621599724, 0.9796558695) -points['ffa_spawn5'] = (-7.46052038, 2.863807079, - 4.936420902) + (0.8707600789, 0.621599724, 2.233577195) + 2.933065031, + 0.621599724, + 0.9796558695, +) +points['ffa_spawn5'] = (-7.46052038, 2.863807079, 4.936420902) + ( + 0.8707600789, + 0.621599724, + 2.233577195, +) points['flag1'] = (7.557928387, 2.889342613, -7.208799596) points['flag2'] = (7.696183956, 1.095466627, 6.103380446) points['flag3'] = (-8.122819332, 2.844893069, 6.103380446) points['flag4'] = (-8.018537918, 2.844893069, -6.202403896) points['flag_default'] = (-7.563673017, 2.850652319, 0.08844978098) -boxes['map_bounds'] = (-0.1916036665, 8.764115729, 0.1971423239) + ( - 0.0, 0.0, 0.0) + (27.41996888, 18.47258973, 22.17335735) +boxes['map_bounds'] = ( + (-0.1916036665, 8.764115729, 0.1971423239) + + (0.0, 0.0, 0.0) + + (27.41996888, 18.47258973, 22.17335735) +) points['powerup_spawn1'] = (7.830495287, 2.115087683, -0.05452287857) points['powerup_spawn2'] = (-5.190293739, 1.476317443, -3.80237889) points['powerup_spawn3'] = (-8.540957726, 3.762979519, -7.27710542) @@ -42,43 +62,97 @@ points['race_mine7'] = (0.969120762, 2.851484105, -7.892038145) points['race_mine8'] = (-2.976299166, 2.851484105, -6.241064664) points['race_mine9'] = (-6.962812986, 2.851484105, -2.120262964) points['race_point1'] = (2.280447713, 1.16512015, 6.015278429) + ( - 0.7066894139, 4.672784871, 1.322422256) + 0.7066894139, + 4.672784871, + 1.322422256, +) points['race_point10'] = (-4.196540687, 2.877461266, -7.106874334) + ( - 0.1057202515, 5.496127671, 1.028552836) + 0.1057202515, + 5.496127671, + 1.028552836, +) points['race_point11'] = (-7.634488499, 2.877461266, -3.61728743) + ( - 1.438144134, 5.157457566, 0.06318119808) + 1.438144134, + 5.157457566, + 0.06318119808, +) points['race_point12'] = (-7.541251512, 2.877461266, 3.290439202) + ( - 1.668578284, 5.52484043, 0.06318119808) -points['race_point2'] = (4.853459878, 1.16512015, - 6.035867283) + (0.3920628436, 4.577066678, 1.34568243) + 1.668578284, + 5.52484043, + 0.06318119808, +) +points['race_point2'] = (4.853459878, 1.16512015, 6.035867283) + ( + 0.3920628436, + 4.577066678, + 1.34568243, +) points['race_point3'] = (6.905234402, 1.16512015, 1.143337503) + ( - 1.611663691, 3.515259775, 0.1135135003) + 1.611663691, + 3.515259775, + 0.1135135003, +) points['race_point4'] = (2.681673258, 1.16512015, 0.771967064) + ( - 0.6475414982, 3.602143342, 0.1135135003) + 0.6475414982, + 3.602143342, + 0.1135135003, +) points['race_point5'] = (-0.3776550727, 1.225615225, 1.920343787) + ( - 0.1057202515, 4.245024435, 0.5914887576) + 0.1057202515, + 4.245024435, + 0.5914887576, +) points['race_point6'] = (-4.365081958, 1.16512015, -0.3565529313) + ( - 1.627090525, 4.549428479, 0.1135135003) + 1.627090525, + 4.549428479, + 0.1135135003, +) points['race_point7'] = (0.4149308672, 1.16512015, -3.394316313) + ( - 0.1057202515, 4.945367833, 1.310190117) + 0.1057202515, + 4.945367833, + 1.310190117, +) points['race_point8'] = (4.27031635, 2.19747021, -3.335165617) + ( - 0.1057202515, 4.389664492, 1.20413595) + 0.1057202515, + 4.389664492, + 1.20413595, +) points['race_point9'] = (2.552998384, 2.877461266, -7.117366939) + ( - 0.1057202515, 5.512312989, 0.9986814472) + 0.1057202515, + 5.512312989, + 0.9986814472, +) points['shadow_lower_bottom'] = (-0.2227795102, 0.2903873918, 2.680075641) points['shadow_lower_top'] = (-0.2227795102, 0.8824975157, 2.680075641) points['shadow_upper_bottom'] = (-0.2227795102, 6.305086402, 2.680075641) points['shadow_upper_top'] = (-0.2227795102, 9.470923628, 2.680075641) -points['spawn1'] = (7.180043217, 2.85596295, -4.407134234) + (0.7629937742, - 1.0, 1.818908238) -points['spawn2'] = (5.880548999, 1.142163379, 6.171168951) + (1.817516622, 1.0, - 0.7724344394) -points['spawn_by_flag1'] = (7.180043217, 2.85596295, - -4.407134234) + (0.7629937742, 1.0, 1.818908238) -points['spawn_by_flag2'] = (5.880548999, 1.142163379, - 6.171168951) + (1.817516622, 1.0, 0.7724344394) -points['spawn_by_flag3'] = (-6.66642559, 3.554416948, - 5.820238985) + (1.097315815, 1.0, 1.285161684) -points['spawn_by_flag4'] = (-6.842951255, 3.554416948, - -6.17429905) + (0.8208434737, 1.0, 1.285161684) +points['spawn1'] = (7.180043217, 2.85596295, -4.407134234) + ( + 0.7629937742, + 1.0, + 1.818908238, +) +points['spawn2'] = (5.880548999, 1.142163379, 6.171168951) + ( + 1.817516622, + 1.0, + 0.7724344394, +) +points['spawn_by_flag1'] = (7.180043217, 2.85596295, -4.407134234) + ( + 0.7629937742, + 1.0, + 1.818908238, +) +points['spawn_by_flag2'] = (5.880548999, 1.142163379, 6.171168951) + ( + 1.817516622, + 1.0, + 0.7724344394, +) +points['spawn_by_flag3'] = (-6.66642559, 3.554416948, 5.820238985) + ( + 1.097315815, + 1.0, + 1.285161684, +) +points['spawn_by_flag4'] = (-6.842951255, 3.554416948, -6.17429905) + ( + 0.8208434737, + 1.0, + 1.285161684, +) points['tnt1'] = (-3.398312776, 2.067056737, -1.90142919) diff --git a/dist/ba_data/python/bastd/mapdata/bridgit.py b/dist/ba_data/python/bastd/mapdata/bridgit.py index 70cb3c7..d6e8994 100644 --- a/dist/ba_data/python/bastd/mapdata/bridgit.py +++ b/dist/ba_data/python/bastd/mapdata/bridgit.py @@ -6,20 +6,34 @@ points = {} # noinspection PyDictCreation boxes = {} -boxes['area_of_interest_bounds'] = (-0.2457963347, 3.828181068, - -1.528362695) + (0.0, 0.0, 0.0) + ( - 19.14849937, 7.312788846, 8.436232726) -points['ffa_spawn1'] = (-5.869295124, 3.715437928, - -1.617274877) + (0.9410329222, 1.0, 1.818908238) -points['ffa_spawn2'] = (5.160809653, 3.761793434, - -1.443012115) + (0.7729807005, 1.0, 1.818908238) -points['ffa_spawn3'] = (-0.4266381164, 3.761793434, - -1.555562653) + (4.034151421, 1.0, 0.2731725824) +boxes['area_of_interest_bounds'] = ( + (-0.2457963347, 3.828181068, -1.528362695) + + (0.0, 0.0, 0.0) + + (19.14849937, 7.312788846, 8.436232726) +) +points['ffa_spawn1'] = (-5.869295124, 3.715437928, -1.617274877) + ( + 0.9410329222, + 1.0, + 1.818908238, +) +points['ffa_spawn2'] = (5.160809653, 3.761793434, -1.443012115) + ( + 0.7729807005, + 1.0, + 1.818908238, +) +points['ffa_spawn3'] = (-0.4266381164, 3.761793434, -1.555562653) + ( + 4.034151421, + 1.0, + 0.2731725824, +) points['flag1'] = (-7.354603923, 3.770769731, -1.617274877) points['flag2'] = (6.885846926, 3.770685211, -1.443012115) points['flag_default'] = (-0.2227795102, 3.802429326, -1.562586233) -boxes['map_bounds'] = (-0.1916036665, 7.481446847, -1.311948055) + ( - 0.0, 0.0, 0.0) + (27.41996888, 18.47258973, 19.52220249) +boxes['map_bounds'] = ( + (-0.1916036665, 7.481446847, -1.311948055) + + (0.0, 0.0, 0.0) + + (27.41996888, 18.47258973, 19.52220249) +) points['powerup_spawn1'] = (6.82849491, 4.658454461, 0.1938139802) points['powerup_spawn2'] = (-7.253381358, 4.728692078, 0.252121017) points['powerup_spawn3'] = (6.82849491, 4.658454461, -3.461765427) @@ -28,7 +42,13 @@ points['shadow_lower_bottom'] = (-0.2227795102, 2.83188898, 2.680075641) points['shadow_lower_top'] = (-0.2227795102, 3.498267184, 2.680075641) points['shadow_upper_bottom'] = (-0.2227795102, 6.305086402, 2.680075641) points['shadow_upper_top'] = (-0.2227795102, 9.470923628, 2.680075641) -points['spawn1'] = (-5.869295124, 3.715437928, - -1.617274877) + (0.9410329222, 1.0, 1.818908238) -points['spawn2'] = (5.160809653, 3.761793434, - -1.443012115) + (0.7729807005, 1.0, 1.818908238) +points['spawn1'] = (-5.869295124, 3.715437928, -1.617274877) + ( + 0.9410329222, + 1.0, + 1.818908238, +) +points['spawn2'] = (5.160809653, 3.761793434, -1.443012115) + ( + 0.7729807005, + 1.0, + 1.818908238, +) diff --git a/dist/ba_data/python/bastd/mapdata/courtyard.py b/dist/ba_data/python/bastd/mapdata/courtyard.py index 634cdb2..9220e05 100644 --- a/dist/ba_data/python/bastd/mapdata/courtyard.py +++ b/dist/ba_data/python/bastd/mapdata/courtyard.py @@ -6,9 +6,11 @@ points = {} # noinspection PyDictCreation boxes = {} -boxes['area_of_interest_bounds'] = (0.3544110667, 3.958431362, - -2.175025358) + (0.0, 0.0, 0.0) + ( - 16.37702017, 7.755670126, 13.38680645) +boxes['area_of_interest_bounds'] = ( + (0.3544110667, 3.958431362, -2.175025358) + + (0.0, 0.0, 0.0) + + (16.37702017, 7.755670126, 13.38680645) +) points['bot_spawn_bottom'] = (-0.06281376545, 2.814769232, 1.95079953) points['bot_spawn_bottom_half_left'] = (-2.05017213, 2.814769232, 1.95079953) points['bot_spawn_bottom_half_right'] = (1.85515704, 2.814769232, 1.95079953) @@ -16,14 +18,12 @@ points['bot_spawn_bottom_left'] = (-3.680966394, 2.814769232, 1.95079953) points['bot_spawn_bottom_right'] = (3.586455826, 2.814769232, 1.95079953) points['bot_spawn_left'] = (-6.447075231, 2.814769232, -2.317996277) points['bot_spawn_left_lower'] = (-6.447075231, 2.814769232, -1.509957962) -points['bot_spawn_left_lower_more'] = (-6.447075231, 2.814769232, - -0.4832205112) +points['bot_spawn_left_lower_more'] = (-6.447075231, 2.814769232, -0.4832205112) points['bot_spawn_left_upper'] = (-6.447075231, 2.814769232, -3.183562653) points['bot_spawn_left_upper_more'] = (-6.447075231, 2.814769232, -4.010007449) points['bot_spawn_right'] = (6.539735433, 2.814769232, -2.317996277) points['bot_spawn_right_lower'] = (6.539735433, 2.814769232, -1.396042829) -points['bot_spawn_right_lower_more'] = (6.539735433, 2.814769232, - -0.3623501424) +points['bot_spawn_right_lower_more'] = (6.539735433, 2.814769232, -0.3623501424) points['bot_spawn_right_upper'] = (6.539735433, 2.814769232, -3.130071083) points['bot_spawn_right_upper_more'] = (6.539735433, 2.814769232, -3.977427131) points['bot_spawn_top'] = (-0.06281376545, 2.814769232, -5.833265855) @@ -34,28 +34,55 @@ points['bot_spawn_top_right'] = (3.396752931, 2.814769232, -5.950680835) points['bot_spawn_turret_bottom_left'] = (-6.127144702, 3.3275475, 1.911189749) points['bot_spawn_turret_bottom_right'] = (6.372913618, 3.3275475, 1.79864574) points['bot_spawn_turret_top_left'] = (-6.127144702, 3.3275475, -6.572879116) -points['bot_spawn_turret_top_middle'] = (0.08149184008, 4.270281808, - -8.522292633) -points['bot_spawn_turret_top_middle_left'] = (-1.271380584, 4.270281808, - -8.522292633) -points['bot_spawn_turret_top_middle_right'] = (1.128462393, 4.270281808, - -8.522292633) +points['bot_spawn_turret_top_middle'] = ( + 0.08149184008, + 4.270281808, + -8.522292633, +) +points['bot_spawn_turret_top_middle_left'] = ( + -1.271380584, + 4.270281808, + -8.522292633, +) +points['bot_spawn_turret_top_middle_right'] = ( + 1.128462393, + 4.270281808, + -8.522292633, +) points['bot_spawn_turret_top_right'] = (6.372913618, 3.3275475, -6.603689486) -boxes['edge_box'] = (0.0, 1.036729365, -2.142494752) + (0.0, 0.0, 0.0) + ( - 12.01667356, 11.40580437, 7.808185564) -points['ffa_spawn1'] = (-6.228613999, 3.765660284, - -5.15969075) + (1.480100328, 1.0, 0.07121651432) -points['ffa_spawn2'] = (6.286481065, 3.765660284, - -4.923207718) + (1.419728931, 1.0, 0.07121651432) -points['ffa_spawn3'] = (-0.01917923364, 4.39873514, - -6.964732605) + (1.505953039, 1.0, 0.2494784408) -points['ffa_spawn4'] = (-0.01917923364, 3.792688047, - 3.453884398) + (4.987737689, 1.0, 0.1505089956) +boxes['edge_box'] = ( + (0.0, 1.036729365, -2.142494752) + + (0.0, 0.0, 0.0) + + (12.01667356, 11.40580437, 7.808185564) +) +points['ffa_spawn1'] = (-6.228613999, 3.765660284, -5.15969075) + ( + 1.480100328, + 1.0, + 0.07121651432, +) +points['ffa_spawn2'] = (6.286481065, 3.765660284, -4.923207718) + ( + 1.419728931, + 1.0, + 0.07121651432, +) +points['ffa_spawn3'] = (-0.01917923364, 4.39873514, -6.964732605) + ( + 1.505953039, + 1.0, + 0.2494784408, +) +points['ffa_spawn4'] = (-0.01917923364, 3.792688047, 3.453884398) + ( + 4.987737689, + 1.0, + 0.1505089956, +) points['flag1'] = (-5.965661853, 2.820013813, -2.428844806) points['flag2'] = (5.905546426, 2.800475393, -2.218272564) points['flag_default'] = (0.2516184246, 2.784213993, -2.644195211) -boxes['map_bounds'] = (0.2608783669, 4.899663734, -3.543675157) + ( - 0.0, 0.0, 0.0) + (29.23565494, 14.19991443, 29.92689344) +boxes['map_bounds'] = ( + (0.2608783669, 4.899663734, -3.543675157) + + (0.0, 0.0, 0.0) + + (29.23565494, 14.19991443, 29.92689344) +) points['powerup_spawn1'] = (-3.555558641, 3.168458621, 0.3692836925) points['powerup_spawn2'] = (3.625691848, 3.168458621, 0.4058534671) points['powerup_spawn3'] = (3.625691848, 3.168458621, -4.987242873) @@ -64,7 +91,13 @@ points['shadow_lower_bottom'] = (0.5236258282, 0.02085132358, 5.341226521) points['shadow_lower_top'] = (0.5236258282, 1.206119006, 5.341226521) points['shadow_upper_bottom'] = (0.5236258282, 6.359015684, 5.341226521) points['shadow_upper_top'] = (0.5236258282, 10.12385584, 5.341226521) -points['spawn1'] = (-7.514831403, 3.803639368, - -2.102145502) + (0.0878727285, 1.0, 2.195980213) -points['spawn2'] = (7.462102032, 3.772786511, - -1.835207267) + (0.0288041898, 1.0, 2.221665995) +points['spawn1'] = (-7.514831403, 3.803639368, -2.102145502) + ( + 0.0878727285, + 1.0, + 2.195980213, +) +points['spawn2'] = (7.462102032, 3.772786511, -1.835207267) + ( + 0.0288041898, + 1.0, + 2.221665995, +) diff --git a/dist/ba_data/python/bastd/mapdata/crag_castle.py b/dist/ba_data/python/bastd/mapdata/crag_castle.py index ca5d794..587002f 100644 --- a/dist/ba_data/python/bastd/mapdata/crag_castle.py +++ b/dist/ba_data/python/bastd/mapdata/crag_castle.py @@ -6,35 +6,65 @@ points = {} # noinspection PyDictCreation boxes = {} -boxes['area_of_interest_bounds'] = (0.7033834902, 6.55869393, -3.153439808) + ( - 0.0, 0.0, 0.0) + (16.73648528, 14.94789935, 11.60063102) +boxes['area_of_interest_bounds'] = ( + (0.7033834902, 6.55869393, -3.153439808) + + (0.0, 0.0, 0.0) + + (16.73648528, 14.94789935, 11.60063102) +) points['ffa_spawn1'] = (-4.04166076, 7.54589296, -3.542792409) + ( - 2.471508516, 1.156019141, 0.1791707664) -points['ffa_spawn2'] = (5.429881832, 7.582951102, - -3.497145747) + (2.415753564, 1.12871694, 0.17898173) -points['ffa_spawn3'] = (4.8635999, 9.311949436, - -6.013939259) + (1.61785329, 1.12871694, 0.17898173) -points['ffa_spawn4'] = (-3.628023052, 9.311949436, - -6.013939259) + (1.61785329, 1.12871694, 0.17898173) -points['ffa_spawn5'] = (-2.414363536, 5.930994442, - 0.03036413701) + (1.61785329, 1.12871694, 0.17898173) -points['ffa_spawn6'] = (3.520989196, 5.930994442, - 0.03036413701) + (1.61785329, 1.12871694, 0.17898173) + 2.471508516, + 1.156019141, + 0.1791707664, +) +points['ffa_spawn2'] = (5.429881832, 7.582951102, -3.497145747) + ( + 2.415753564, + 1.12871694, + 0.17898173, +) +points['ffa_spawn3'] = (4.8635999, 9.311949436, -6.013939259) + ( + 1.61785329, + 1.12871694, + 0.17898173, +) +points['ffa_spawn4'] = (-3.628023052, 9.311949436, -6.013939259) + ( + 1.61785329, + 1.12871694, + 0.17898173, +) +points['ffa_spawn5'] = (-2.414363536, 5.930994442, 0.03036413701) + ( + 1.61785329, + 1.12871694, + 0.17898173, +) +points['ffa_spawn6'] = (3.520989196, 5.930994442, 0.03036413701) + ( + 1.61785329, + 1.12871694, + 0.17898173, +) points['flag1'] = (-1.900164924, 9.363050076, -6.441041548) points['flag2'] = (3.240019982, 9.319215955, -6.392759924) points['flag3'] = (-6.883672142, 7.475761129, 0.2098388241) points['flag4'] = (8.193957063, 7.478129652, 0.1536410508) points['flag_default'] = (0.6296142785, 6.221901832, -0.0435909658) -boxes['map_bounds'] = (0.4799042306, 9.085075529, -3.267604531) + ( - 0.0, 0.0, 0.0) + (22.9573075, 9.908550511, 14.17997333) +boxes['map_bounds'] = ( + (0.4799042306, 9.085075529, -3.267604531) + + (0.0, 0.0, 0.0) + + (22.9573075, 9.908550511, 14.17997333) +) points['powerup_spawn1'] = (7.916483636, 7.83853949, -5.990841203) points['powerup_spawn2'] = (-0.6978591232, 7.883836528, -6.066674247) points['powerup_spawn3'] = (1.858093733, 7.893059862, -6.076932659) points['powerup_spawn4'] = (-6.671997388, 7.992307645, -6.121432603) -points['spawn1'] = (-5.169730601, 7.54589296, - -3.542792409) + (1.057384557, 1.156019141, 0.1791707664) -points['spawn2'] = (6.203092708, 7.582951102, - -3.497145747) + (1.009865407, 1.12871694, 0.17898173) +points['spawn1'] = (-5.169730601, 7.54589296, -3.542792409) + ( + 1.057384557, + 1.156019141, + 0.1791707664, +) +points['spawn2'] = (6.203092708, 7.582951102, -3.497145747) + ( + 1.009865407, + 1.12871694, + 0.17898173, +) points['spawn_by_flag1'] = (-2.872146219, 9.363050076, -6.041110823) points['spawn_by_flag2'] = (4.313355684, 9.363050076, -6.041110823) points['spawn_by_flag3'] = (-6.634074097, 7.508585058, -0.5918910315) diff --git a/dist/ba_data/python/bastd/mapdata/doom_shroom.py b/dist/ba_data/python/bastd/mapdata/doom_shroom.py index 634a730..a6a5d49 100644 --- a/dist/ba_data/python/bastd/mapdata/doom_shroom.py +++ b/dist/ba_data/python/bastd/mapdata/doom_shroom.py @@ -6,22 +6,39 @@ points = {} # noinspection PyDictCreation boxes = {} -boxes['area_of_interest_bounds'] = (0.4687647786, 2.320345088, - -3.219423694) + (0.0, 0.0, 0.0) + ( - 21.34898078, 10.25529817, 14.67298352) -points['ffa_spawn1'] = (-5.828122667, 2.301094498, - -3.445694701) + (1.0, 1.0, 2.682935578) -points['ffa_spawn2'] = (6.496252674, 2.397778847, -3.573241388) + (1.0, 1.0, - 2.682935578) -points['ffa_spawn3'] = (0.8835145921, 2.307217208, - -0.3552854962) + (4.455517747, 1.0, 0.2723037175) -points['ffa_spawn4'] = (0.8835145921, 2.307217208, - -7.124335491) + (4.455517747, 1.0, 0.2723037175) +boxes['area_of_interest_bounds'] = ( + (0.4687647786, 2.320345088, -3.219423694) + + (0.0, 0.0, 0.0) + + (21.34898078, 10.25529817, 14.67298352) +) +points['ffa_spawn1'] = (-5.828122667, 2.301094498, -3.445694701) + ( + 1.0, + 1.0, + 2.682935578, +) +points['ffa_spawn2'] = (6.496252674, 2.397778847, -3.573241388) + ( + 1.0, + 1.0, + 2.682935578, +) +points['ffa_spawn3'] = (0.8835145921, 2.307217208, -0.3552854962) + ( + 4.455517747, + 1.0, + 0.2723037175, +) +points['ffa_spawn4'] = (0.8835145921, 2.307217208, -7.124335491) + ( + 4.455517747, + 1.0, + 0.2723037175, +) points['flag1'] = (-7.153737138, 2.251993091, -3.427368878) points['flag2'] = (8.103769491, 2.320591215, -3.548878069) points['flag_default'] = (0.5964565429, 2.373456481, -4.241969517) -boxes['map_bounds'] = (0.4566560559, 1.332051421, -3.80651373) + ( - 0.0, 0.0, 0.0) + (27.75073129, 14.44528216, 22.9896617) +boxes['map_bounds'] = ( + (0.4566560559, 1.332051421, -3.80651373) + + (0.0, 0.0, 0.0) + + (27.75073129, 14.44528216, 22.9896617) +) points['powerup_spawn1'] = (5.180858712, 4.278900266, -7.282758712) points['powerup_spawn2'] = (-3.236908759, 4.159702067, -0.3232556512) points['powerup_spawn3'] = (5.082843398, 4.159702067, -0.3232556512) @@ -30,7 +47,13 @@ points['shadow_lower_bottom'] = (0.5964565429, -0.2279530265, 3.368035253) points['shadow_lower_top'] = (0.5964565429, 0.6982784189, 3.368035253) points['shadow_upper_bottom'] = (0.5964565429, 5.413250948, 3.368035253) points['shadow_upper_top'] = (0.5964565429, 7.891484473, 3.368035253) -points['spawn1'] = (-5.828122667, 2.301094498, -3.445694701) + (1.0, 1.0, - 2.682935578) -points['spawn2'] = (6.496252674, 2.397778847, -3.573241388) + (1.0, 1.0, - 2.682935578) +points['spawn1'] = (-5.828122667, 2.301094498, -3.445694701) + ( + 1.0, + 1.0, + 2.682935578, +) +points['spawn2'] = (6.496252674, 2.397778847, -3.573241388) + ( + 1.0, + 1.0, + 2.682935578, +) diff --git a/dist/ba_data/python/bastd/mapdata/football_stadium.py b/dist/ba_data/python/bastd/mapdata/football_stadium.py index b650238..6e490c4 100644 --- a/dist/ba_data/python/bastd/mapdata/football_stadium.py +++ b/dist/ba_data/python/bastd/mapdata/football_stadium.py @@ -6,23 +6,42 @@ points = {} # noinspection PyDictCreation boxes = {} -boxes['area_of_interest_bounds'] = (0.0, 1.185751251, 0.4326226188) + ( - 0.0, 0.0, 0.0) + (29.8180273, 11.57249038, 18.89134176) -boxes['edge_box'] = (-0.103873591, 0.4133341891, 0.4294651013) + ( - 0.0, 0.0, 0.0) + (22.48295719, 1.290242794, 8.990252454) -points['ffa_spawn1'] = (-0.08015551329, 0.02275111462, - -4.373674593) + (8.895057015, 1.0, 0.444350722) -points['ffa_spawn2'] = (-0.08015551329, 0.02275111462, - 4.076288941) + (8.895057015, 1.0, 0.444350722) +boxes['area_of_interest_bounds'] = ( + (0.0, 1.185751251, 0.4326226188) + + (0.0, 0.0, 0.0) + + (29.8180273, 11.57249038, 18.89134176) +) +boxes['edge_box'] = ( + (-0.103873591, 0.4133341891, 0.4294651013) + + (0.0, 0.0, 0.0) + + (22.48295719, 1.290242794, 8.990252454) +) +points['ffa_spawn1'] = (-0.08015551329, 0.02275111462, -4.373674593) + ( + 8.895057015, + 1.0, + 0.444350722, +) +points['ffa_spawn2'] = (-0.08015551329, 0.02275111462, 4.076288941) + ( + 8.895057015, + 1.0, + 0.444350722, +) points['flag1'] = (-10.99027878, 0.05744967453, 0.1095578275) points['flag2'] = (11.01486398, 0.03986567039, 0.1095578275) points['flag_default'] = (-0.1001374046, 0.04180340146, 0.1095578275) -boxes['goal1'] = (12.22454533, 1.0, - 0.1087926362) + (0.0, 0.0, 0.0) + (2.0, 2.0, 12.97466313) -boxes['goal2'] = (-12.15961605, 1.0, - 0.1097860203) + (0.0, 0.0, 0.0) + (2.0, 2.0, 13.11856424) -boxes['map_bounds'] = (0.0, 1.185751251, 0.4326226188) + (0.0, 0.0, 0.0) + ( - 42.09506485, 22.81173179, 29.76723155) +boxes['goal1'] = ( + (12.22454533, 1.0, 0.1087926362) + (0.0, 0.0, 0.0) + (2.0, 2.0, 12.97466313) +) +boxes['goal2'] = ( + (-12.15961605, 1.0, 0.1097860203) + + (0.0, 0.0, 0.0) + + (2.0, 2.0, 13.11856424) +) +boxes['map_bounds'] = ( + (0.0, 1.185751251, 0.4326226188) + + (0.0, 0.0, 0.0) + + (42.09506485, 22.81173179, 29.76723155) +) points['powerup_spawn1'] = (5.414681236, 0.9515026107, -5.037912441) points['powerup_spawn2'] = (-5.555402285, 0.9515026107, -5.037912441) points['powerup_spawn3'] = (5.414681236, 0.9515026107, 5.148223181) diff --git a/dist/ba_data/python/bastd/mapdata/happy_thoughts.py b/dist/ba_data/python/bastd/mapdata/happy_thoughts.py index c3e03ab..164bdb2 100644 --- a/dist/ba_data/python/bastd/mapdata/happy_thoughts.py +++ b/dist/ba_data/python/bastd/mapdata/happy_thoughts.py @@ -6,41 +6,82 @@ points = {} # noinspection PyDictCreation boxes = {} -boxes['area_of_interest_bounds'] = (-1.045859963, 12.67722855, - -5.401537075) + (0.0, 0.0, 0.0) + ( - 34.46156851, 20.94044653, 0.6931564611) -points['ffa_spawn1'] = (-9.295167711, 8.010664315, - -5.44451005) + (1.555840357, 1.453808816, 0.1165648888) +boxes['area_of_interest_bounds'] = ( + (-1.045859963, 12.67722855, -5.401537075) + + (0.0, 0.0, 0.0) + + (34.46156851, 20.94044653, 0.6931564611) +) +points['ffa_spawn1'] = (-9.295167711, 8.010664315, -5.44451005) + ( + 1.555840357, + 1.453808816, + 0.1165648888, +) points['ffa_spawn2'] = (7.484707127, 8.172681752, -5.614479365) + ( - 1.553861796, 1.453808816, 0.04419853907) + 1.553861796, + 1.453808816, + 0.04419853907, +) points['ffa_spawn3'] = (9.55724115, 11.30789446, -5.614479365) + ( - 1.337925849, 1.453808816, 0.04419853907) + 1.337925849, + 1.453808816, + 0.04419853907, +) points['ffa_spawn4'] = (-11.55747023, 10.99170684, -5.614479365) + ( - 1.337925849, 1.453808816, 0.04419853907) + 1.337925849, + 1.453808816, + 0.04419853907, +) points['ffa_spawn5'] = (-1.878892369, 9.46490571, -5.614479365) + ( - 1.337925849, 1.453808816, 0.04419853907) + 1.337925849, + 1.453808816, + 0.04419853907, +) points['ffa_spawn6'] = (-0.4912812943, 5.077006397, -5.521672101) + ( - 1.878332089, 1.453808816, 0.007578097856) + 1.878332089, + 1.453808816, + 0.007578097856, +) points['flag1'] = (-11.75152479, 8.057427485, -5.52) points['flag2'] = (9.840909039, 8.188634282, -5.52) points['flag3'] = (-0.2195258696, 5.010273907, -5.52) points['flag4'] = (-0.04605809154, 12.73369108, -5.52) points['flag_default'] = (-0.04201942896, 12.72374492, -5.52) -boxes['map_bounds'] = (-0.8748348681, 9.212941713, -5.729538885) + ( - 0.0, 0.0, 0.0) + (36.09666006, 26.19950145, 7.89541168) +boxes['map_bounds'] = ( + (-0.8748348681, 9.212941713, -5.729538885) + + (0.0, 0.0, 0.0) + + (36.09666006, 26.19950145, 7.89541168) +) points['powerup_spawn1'] = (1.160232442, 6.745963662, -5.469115985) points['powerup_spawn2'] = (-1.899700206, 10.56447241, -5.505721177) points['powerup_spawn3'] = (10.56098871, 12.25165669, -5.576232453) points['powerup_spawn4'] = (-12.33530337, 12.25165669, -5.576232453) -points['spawn1'] = (-9.295167711, 8.010664315, - -5.44451005) + (1.555840357, 1.453808816, 0.1165648888) -points['spawn2'] = (7.484707127, 8.172681752, - -5.614479365) + (1.553861796, 1.453808816, 0.04419853907) +points['spawn1'] = (-9.295167711, 8.010664315, -5.44451005) + ( + 1.555840357, + 1.453808816, + 0.1165648888, +) +points['spawn2'] = (7.484707127, 8.172681752, -5.614479365) + ( + 1.553861796, + 1.453808816, + 0.04419853907, +) points['spawn_by_flag1'] = (-9.295167711, 8.010664315, -5.44451005) + ( - 1.555840357, 1.453808816, 0.1165648888) + 1.555840357, + 1.453808816, + 0.1165648888, +) points['spawn_by_flag2'] = (7.484707127, 8.172681752, -5.614479365) + ( - 1.553861796, 1.453808816, 0.04419853907) + 1.553861796, + 1.453808816, + 0.04419853907, +) points['spawn_by_flag3'] = (-1.45994593, 5.038762459, -5.535288724) + ( - 0.9516389866, 0.6666414677, 0.08607244075) + 0.9516389866, + 0.6666414677, + 0.08607244075, +) points['spawn_by_flag4'] = (0.4932087091, 12.74493212, -5.598987003) + ( - 0.5245740665, 0.5245740665, 0.01941146064) + 0.5245740665, + 0.5245740665, + 0.01941146064, +) diff --git a/dist/ba_data/python/bastd/mapdata/hockey_stadium.py b/dist/ba_data/python/bastd/mapdata/hockey_stadium.py index 722797f..9f00e49 100644 --- a/dist/ba_data/python/bastd/mapdata/hockey_stadium.py +++ b/dist/ba_data/python/bastd/mapdata/hockey_stadium.py @@ -6,19 +6,31 @@ points = {} # noinspection PyDictCreation boxes = {} -boxes['area_of_interest_bounds'] = (0.0, 0.7956858119, 0.0) + ( - 0.0, 0.0, 0.0) + (30.80223883, 0.5961646365, 13.88431707) -points['ffa_spawn1'] = (-0.001925625146, 0.02305323209, - -3.81971842) + (7.828121539, 1.0, 0.1588021252) -points['ffa_spawn2'] = (-0.001925625146, 0.02305323209, - 3.560115735) + (7.828121539, 1.0, 0.05859841271) +boxes['area_of_interest_bounds'] = ( + (0.0, 0.7956858119, 0.0) + + (0.0, 0.0, 0.0) + + (30.80223883, 0.5961646365, 13.88431707) +) +points['ffa_spawn1'] = (-0.001925625146, 0.02305323209, -3.81971842) + ( + 7.828121539, + 1.0, + 0.1588021252, +) +points['ffa_spawn2'] = (-0.001925625146, 0.02305323209, 3.560115735) + ( + 7.828121539, + 1.0, + 0.05859841271, +) points['flag1'] = (-11.21689747, 0.09527878981, -0.07659307272) points['flag2'] = (11.08204909, 0.04119542459, -0.07659307272) points['flag_default'] = (-0.01690735171, 0.06139940044, -0.07659307272) boxes['goal1'] = (8.45, 1.0, 0.0) + (0.0, 0.0, 0.0) + (0.4334079123, 1.6, 3.0) boxes['goal2'] = (-8.45, 1.0, 0.0) + (0.0, 0.0, 0.0) + (0.4334079123, 1.6, 3.0) -boxes['map_bounds'] = (0.0, 0.7956858119, -0.4689020853) + (0.0, 0.0, 0.0) + ( - 35.16182389, 12.18696164, 21.52869693) +boxes['map_bounds'] = ( + (0.0, 0.7956858119, -0.4689020853) + + (0.0, 0.0, 0.0) + + (35.16182389, 12.18696164, 21.52869693) +) points['powerup_spawn1'] = (-3.654355317, 1.080990833, -4.765886164) points['powerup_spawn2'] = (-3.654355317, 1.080990833, 4.599802158) points['powerup_spawn3'] = (2.881071011, 1.080990833, -4.765886164) diff --git a/dist/ba_data/python/bastd/mapdata/lake_frigid.py b/dist/ba_data/python/bastd/mapdata/lake_frigid.py index a56b174..4f5fbe7 100644 --- a/dist/ba_data/python/bastd/mapdata/lake_frigid.py +++ b/dist/ba_data/python/bastd/mapdata/lake_frigid.py @@ -6,21 +6,39 @@ points = {} # noinspection PyDictCreation boxes = {} -boxes['area_of_interest_bounds'] = (0.622753268, 3.958431362, -2.48708008) + ( - 0.0, 0.0, 0.0) + (20.62310543, 7.755670126, 12.33155049) -points['ffa_spawn1'] = (-5.782222813, 2.601256429, - -2.116763055) + (0.4872664751, 1.0, 2.99296869) -points['ffa_spawn2'] = (8.331810793, 2.563661107, - -2.362712466) + (0.4929678379, 1.0, 2.590481339) -points['ffa_spawn3'] = (-0.01917923364, 2.623757527, - -6.518902459) + (4.450854686, 1.0, 0.2494784408) -points['ffa_spawn4'] = (-0.01917923364, 2.620884201, - 2.154362669) + (4.987737689, 1.0, 0.1505089956) +boxes['area_of_interest_bounds'] = ( + (0.622753268, 3.958431362, -2.48708008) + + (0.0, 0.0, 0.0) + + (20.62310543, 7.755670126, 12.33155049) +) +points['ffa_spawn1'] = (-5.782222813, 2.601256429, -2.116763055) + ( + 0.4872664751, + 1.0, + 2.99296869, +) +points['ffa_spawn2'] = (8.331810793, 2.563661107, -2.362712466) + ( + 0.4929678379, + 1.0, + 2.590481339, +) +points['ffa_spawn3'] = (-0.01917923364, 2.623757527, -6.518902459) + ( + 4.450854686, + 1.0, + 0.2494784408, +) +points['ffa_spawn4'] = (-0.01917923364, 2.620884201, 2.154362669) + ( + 4.987737689, + 1.0, + 0.1505089956, +) points['flag1'] = (-5.965661853, 2.60868975, -2.428844806) points['flag2'] = (7.469054879, 2.600634569, -2.218272564) points['flag_default'] = (0.5814687904, 2.593249132, -6.083520531) -boxes['map_bounds'] = (0.6679698457, 6.090222998, -2.478650859) + ( - 0.0, 0.0, 0.0) + (26.78420476, 12.49722958, 19.09355242) +boxes['map_bounds'] = ( + (0.6679698457, 6.090222998, -2.478650859) + + (0.0, 0.0, 0.0) + + (26.78420476, 12.49722958, 19.09355242) +) points['powerup_spawn1'] = (-3.178773331, 3.168458621, 1.526824762) points['powerup_spawn2'] = (3.625691848, 3.168458621, 1.563394537) points['powerup_spawn3'] = (3.625691848, 3.168458621, -5.768903171) @@ -52,26 +70,56 @@ points['race_mine7'] = (-4.356878305, 2.523837916, -2.04510117) points['race_mine8'] = (-0.713193354, 2.523837916, -0.1340958729) points['race_mine9'] = (-0.713193354, 2.523837916, 1.275675237) points['race_point1'] = (0.5901776337, 2.544287937, 1.543598704) + ( - 0.2824957007, 3.950514538, 2.292534365) -points['race_point2'] = (4.7526567, 2.489758467, - 1.09551316) + (0.2824957007, 3.950514538, 2.392880724) + 0.2824957007, + 3.950514538, + 2.292534365, +) +points['race_point2'] = (4.7526567, 2.489758467, 1.09551316) + ( + 0.2824957007, + 3.950514538, + 2.392880724, +) points['race_point3'] = (7.450800117, 2.601570758, -2.248040576) + ( - 2.167067932, 3.950514538, 0.2574992262) + 2.167067932, + 3.950514538, + 0.2574992262, +) points['race_point4'] = (5.064768438, 2.489758467, -5.820463576) + ( - 0.2824957007, 3.950514538, 2.392880724) + 0.2824957007, + 3.950514538, + 2.392880724, +) points['race_point5'] = (0.5901776337, 2.67667329, -6.165424036) + ( - 0.2824957007, 3.950514538, 2.156382533) + 0.2824957007, + 3.950514538, + 2.156382533, +) points['race_point6'] = (-3.057459058, 2.489758467, -6.114179652) + ( - 0.2824957007, 3.950514538, 2.323773344) -points['race_point7'] = (-5.814316926, 2.57969886, - -2.248040576) + (2.0364457, 3.950514538, 0.2574992262) + 0.2824957007, + 3.950514538, + 2.323773344, +) +points['race_point7'] = (-5.814316926, 2.57969886, -2.248040576) + ( + 2.0364457, + 3.950514538, + 0.2574992262, +) points['race_point8'] = (-2.958397223, 2.489758467, 1.360005754) + ( - 0.2824957007, 3.950514538, 2.529692681) + 0.2824957007, + 3.950514538, + 2.529692681, +) points['shadow_lower_bottom'] = (0.5236258282, 1.516338013, 5.341226521) points['shadow_lower_top'] = (0.5236258282, 2.516776651, 5.341226521) points['shadow_upper_bottom'] = (0.5236258282, 4.543246769, 5.341226521) points['shadow_upper_top'] = (0.5236258282, 5.917963067, 5.341226521) -points['spawn1'] = (-5.945079307, 2.524666031, - -2.102145502) + (0.0878727285, 1.0, 2.195980213) -points['spawn2'] = (8.079733391, 2.506883995, - -2.364598145) + (0.0288041898, 1.0, 2.221665995) +points['spawn1'] = (-5.945079307, 2.524666031, -2.102145502) + ( + 0.0878727285, + 1.0, + 2.195980213, +) +points['spawn2'] = (8.079733391, 2.506883995, -2.364598145) + ( + 0.0288041898, + 1.0, + 2.221665995, +) diff --git a/dist/ba_data/python/bastd/mapdata/monkey_face.py b/dist/ba_data/python/bastd/mapdata/monkey_face.py index b041509..274f58e 100644 --- a/dist/ba_data/python/bastd/mapdata/monkey_face.py +++ b/dist/ba_data/python/bastd/mapdata/monkey_face.py @@ -6,22 +6,39 @@ points = {} # noinspection PyDictCreation boxes = {} -boxes['area_of_interest_bounds'] = (-1.657177611, 4.132574186, - -1.580485661) + (0.0, 0.0, 0.0) + ( - 17.36258946, 10.49020453, 12.31460338) +boxes['area_of_interest_bounds'] = ( + (-1.657177611, 4.132574186, -1.580485661) + + (0.0, 0.0, 0.0) + + (17.36258946, 10.49020453, 12.31460338) +) points['ffa_spawn1'] = (-8.026373566, 3.349937889, -2.542088202) + ( - 0.9450583628, 0.9450583628, 1.181509268) -points['ffa_spawn2'] = (4.73470012, 3.308679998, - -2.757871588) + (0.9335931003, 1.0, 1.217352295) -points['ffa_spawn3'] = (-1.907161509, 3.326830784, - -6.572223028) + (4.080767643, 1.0, 0.2880331593) -points['ffa_spawn4'] = (-1.672823345, 3.326830784, - 2.405442985) + (3.870724402, 1.0, 0.2880331593) + 0.9450583628, + 0.9450583628, + 1.181509268, +) +points['ffa_spawn2'] = (4.73470012, 3.308679998, -2.757871588) + ( + 0.9335931003, + 1.0, + 1.217352295, +) +points['ffa_spawn3'] = (-1.907161509, 3.326830784, -6.572223028) + ( + 4.080767643, + 1.0, + 0.2880331593, +) +points['ffa_spawn4'] = (-1.672823345, 3.326830784, 2.405442985) + ( + 3.870724402, + 1.0, + 0.2880331593, +) points['flag1'] = (-8.968414135, 3.35709348, -2.804123917) points['flag2'] = (5.945128279, 3.354825248, -2.663635497) points['flag_default'] = (-1.688166134, 3.392387172, -2.238613943) -boxes['map_bounds'] = (-1.615296127, 6.825502312, -2.200965435) + ( - 0.0, 0.0, 0.0) + (22.51905077, 12.21074608, 15.9079565) +boxes['map_bounds'] = ( + (-1.615296127, 6.825502312, -2.200965435) + + (0.0, 0.0, 0.0) + + (22.51905077, 12.21074608, 15.9079565) +) points['powerup_spawn1'] = (-6.859406739, 4.429165244, -6.588618549) points['powerup_spawn2'] = (-5.422572086, 4.228850685, 2.803988636) points['powerup_spawn3'] = (3.148493267, 4.429165244, -6.588618549) @@ -30,7 +47,13 @@ points['shadow_lower_bottom'] = (-1.877364768, 0.9878677276, 5.50201662) points['shadow_lower_top'] = (-1.877364768, 2.881511768, 5.50201662) points['shadow_upper_bottom'] = (-1.877364768, 6.169020542, 5.50201662) points['shadow_upper_top'] = (-1.877364768, 10.2492777, 5.50201662) -points['spawn1'] = (-8.026373566, 3.349937889, - -2.542088202) + (0.9450583628, 0.9450583628, 1.181509268) -points['spawn2'] = (4.73470012, 3.308679998, -2.757871588) + (0.9335931003, - 1.0, 1.217352295) +points['spawn1'] = (-8.026373566, 3.349937889, -2.542088202) + ( + 0.9450583628, + 0.9450583628, + 1.181509268, +) +points['spawn2'] = (4.73470012, 3.308679998, -2.757871588) + ( + 0.9335931003, + 1.0, + 1.217352295, +) diff --git a/dist/ba_data/python/bastd/mapdata/rampage.py b/dist/ba_data/python/bastd/mapdata/rampage.py index 1daff2d..f1e96a2 100644 --- a/dist/ba_data/python/bastd/mapdata/rampage.py +++ b/dist/ba_data/python/bastd/mapdata/rampage.py @@ -6,27 +6,47 @@ points = {} # noinspection PyDictCreation boxes = {} -boxes['area_of_interest_bounds'] = (0.3544110667, 5.616383286, - -4.066055072) + (0.0, 0.0, 0.0) + ( - 19.90053969, 10.34051135, 8.16221072) -boxes['edge_box'] = (0.3544110667, 5.438284793, -4.100357672) + ( - 0.0, 0.0, 0.0) + (12.57718032, 4.645176013, 3.605557343) -points['ffa_spawn1'] = (0.5006944438, 5.051501304, - -5.79356326) + (6.626174027, 1.0, 0.3402012662) -points['ffa_spawn2'] = (0.5006944438, 5.051501304, - -2.435321368) + (6.626174027, 1.0, 0.3402012662) +boxes['area_of_interest_bounds'] = ( + (0.3544110667, 5.616383286, -4.066055072) + + (0.0, 0.0, 0.0) + + (19.90053969, 10.34051135, 8.16221072) +) +boxes['edge_box'] = ( + (0.3544110667, 5.438284793, -4.100357672) + + (0.0, 0.0, 0.0) + + (12.57718032, 4.645176013, 3.605557343) +) +points['ffa_spawn1'] = (0.5006944438, 5.051501304, -5.79356326) + ( + 6.626174027, + 1.0, + 0.3402012662, +) +points['ffa_spawn2'] = (0.5006944438, 5.051501304, -2.435321368) + ( + 6.626174027, + 1.0, + 0.3402012662, +) points['flag1'] = (-5.885814199, 5.112162255, -4.251754911) points['flag2'] = (6.700855451, 5.10270501, -4.259912982) points['flag_default'] = (0.3196701116, 5.110914413, -4.292515158) -boxes['map_bounds'] = (0.4528955042, 4.899663734, -3.543675157) + ( - 0.0, 0.0, 0.0) + (23.54502348, 14.19991443, 12.08017448) +boxes['map_bounds'] = ( + (0.4528955042, 4.899663734, -3.543675157) + + (0.0, 0.0, 0.0) + + (23.54502348, 14.19991443, 12.08017448) +) points['powerup_spawn1'] = (-2.645358507, 6.426340583, -4.226597191) points['powerup_spawn2'] = (3.540102796, 6.549722855, -4.198476335) points['shadow_lower_bottom'] = (5.580073911, 3.136491026, 5.341226521) points['shadow_lower_top'] = (5.580073911, 4.321758709, 5.341226521) points['shadow_upper_bottom'] = (5.274539479, 8.425373402, 5.341226521) points['shadow_upper_top'] = (5.274539479, 11.93458162, 5.341226521) -points['spawn1'] = (-4.745706238, 5.051501304, - -4.247934288) + (0.9186962739, 1.0, 0.5153189341) -points['spawn2'] = (5.838590388, 5.051501304, - -4.259627405) + (0.9186962739, 1.0, 0.5153189341) +points['spawn1'] = (-4.745706238, 5.051501304, -4.247934288) + ( + 0.9186962739, + 1.0, + 0.5153189341, +) +points['spawn2'] = (5.838590388, 5.051501304, -4.259627405) + ( + 0.9186962739, + 1.0, + 0.5153189341, +) diff --git a/dist/ba_data/python/bastd/mapdata/roundabout.py b/dist/ba_data/python/bastd/mapdata/roundabout.py index e9f7bdb..084cb70 100644 --- a/dist/ba_data/python/bastd/mapdata/roundabout.py +++ b/dist/ba_data/python/bastd/mapdata/roundabout.py @@ -6,27 +6,48 @@ points = {} # noinspection PyDictCreation boxes = {} -boxes['area_of_interest_bounds'] = (-1.552280404, 3.189001207, -2.40908495) + ( - 0.0, 0.0, 0.0) + (11.96255385, 8.857531648, 9.531689995) -points['ffa_spawn1'] = (-4.056288044, 3.85970651, - -4.6096757) + (0.9393824595, 1.0, 1.422669346) -points['ffa_spawn2'] = (0.9091263403, 3.849381394, - -4.673201431) + (0.9179219809, 1.0, 1.422669346) -points['ffa_spawn3'] = (-1.50312174, 1.498336991, - -0.7271163774) + (5.733928927, 1.0, 0.1877531607) +boxes['area_of_interest_bounds'] = ( + (-1.552280404, 3.189001207, -2.40908495) + + (0.0, 0.0, 0.0) + + (11.96255385, 8.857531648, 9.531689995) +) +points['ffa_spawn1'] = (-4.056288044, 3.85970651, -4.6096757) + ( + 0.9393824595, + 1.0, + 1.422669346, +) +points['ffa_spawn2'] = (0.9091263403, 3.849381394, -4.673201431) + ( + 0.9179219809, + 1.0, + 1.422669346, +) +points['ffa_spawn3'] = (-1.50312174, 1.498336991, -0.7271163774) + ( + 5.733928927, + 1.0, + 0.1877531607, +) points['flag1'] = (-3.01567985, 3.846779683, -6.702828912) points['flag2'] = (-0.01282460768, 3.828492613, -6.684991743) points['flag_default'] = (-1.509110449, 1.447854976, -1.440324146) -boxes['map_bounds'] = (-1.615296127, 8.764115729, -2.663738363) + ( - 0.0, 0.0, 0.0) + (20.48886392, 18.92340529, 13.79786814) +boxes['map_bounds'] = ( + (-1.615296127, 8.764115729, -2.663738363) + + (0.0, 0.0, 0.0) + + (20.48886392, 18.92340529, 13.79786814) +) points['powerup_spawn1'] = (-6.794510156, 2.660340814, 0.01205780317) points['powerup_spawn2'] = (3.611953494, 2.660340814, 0.01205780317) points['shadow_lower_bottom'] = (-1.848173322, 0.6339980822, 2.267036343) points['shadow_lower_top'] = (-1.848173322, 1.077175164, 2.267036343) points['shadow_upper_bottom'] = (-1.848173322, 6.04794944, 2.267036343) points['shadow_upper_top'] = (-1.848173322, 9.186681264, 2.267036343) -points['spawn1'] = (-4.056288044, 3.85970651, -4.6096757) + (0.9393824595, 1.0, - 1.422669346) -points['spawn2'] = (0.9091263403, 3.849381394, - -4.673201431) + (0.9179219809, 1.0, 1.422669346) +points['spawn1'] = (-4.056288044, 3.85970651, -4.6096757) + ( + 0.9393824595, + 1.0, + 1.422669346, +) +points['spawn2'] = (0.9091263403, 3.849381394, -4.673201431) + ( + 0.9179219809, + 1.0, + 1.422669346, +) points['tnt1'] = (-1.509110449, 2.457517361, 0.2340271555) diff --git a/dist/ba_data/python/bastd/mapdata/step_right_up.py b/dist/ba_data/python/bastd/mapdata/step_right_up.py index 5d709bd..b214840 100644 --- a/dist/ba_data/python/bastd/mapdata/step_right_up.py +++ b/dist/ba_data/python/bastd/mapdata/step_right_up.py @@ -6,23 +6,41 @@ points = {} # noinspection PyDictCreation boxes = {} -boxes['area_of_interest_bounds'] = (0.3544110667, 6.07676405, -2.271833016) + ( - 0.0, 0.0, 0.0) + (22.55121262, 10.14644532, 14.66087273) -points['ffa_spawn1'] = (-6.989214197, 5.824902099, - -4.003708602) + (0.4063164993, 1.0, 3.6294637) -points['ffa_spawn2'] = (7.305179278, 5.866583139, - -4.003708602) + (0.4063164993, 1.0, 3.6294637) -points['ffa_spawn3'] = (2.641427041, 4.793721175, - -4.003708602) + (0.4063164993, 1.0, 3.6294637) -points['ffa_spawn4'] = (-2.36228023, 4.793721175, - -4.003708602) + (0.4063164993, 1.0, 3.6294637) +boxes['area_of_interest_bounds'] = ( + (0.3544110667, 6.07676405, -2.271833016) + + (0.0, 0.0, 0.0) + + (22.55121262, 10.14644532, 14.66087273) +) +points['ffa_spawn1'] = (-6.989214197, 5.824902099, -4.003708602) + ( + 0.4063164993, + 1.0, + 3.6294637, +) +points['ffa_spawn2'] = (7.305179278, 5.866583139, -4.003708602) + ( + 0.4063164993, + 1.0, + 3.6294637, +) +points['ffa_spawn3'] = (2.641427041, 4.793721175, -4.003708602) + ( + 0.4063164993, + 1.0, + 3.6294637, +) +points['ffa_spawn4'] = (-2.36228023, 4.793721175, -4.003708602) + ( + 0.4063164993, + 1.0, + 3.6294637, +) points['flag1'] = (-6.005199892, 5.824953504, -8.182477108) points['flag2'] = (6.671556926, 5.819873617, -0.3196373496) points['flag3'] = (-2.105198862, 4.785722143, -3.938339596) points['flag4'] = (2.693244393, 4.785722143, -3.938339596) points['flag_default'] = (0.2516184246, 4.163099318, -3.691279318) -boxes['map_bounds'] = (0.2608783669, 4.899663734, -3.543675157) + ( - 0.0, 0.0, 0.0) + (29.23565494, 14.19991443, 29.92689344) +boxes['map_bounds'] = ( + (0.2608783669, 4.899663734, -3.543675157) + + (0.0, 0.0, 0.0) + + (29.23565494, 14.19991443, 29.92689344) +) points['powerup_spawn1'] = (-5.250579743, 4.725015518, 2.81876755) points['powerup_spawn2'] = (5.694682017, 4.725015518, 2.81876755) points['powerup_spawn3'] = (7.897510583, 6.314188898, -0.7152878923) @@ -33,16 +51,34 @@ points['shadow_lower_bottom'] = (0.5236258282, 2.599698775, 5.341226521) points['shadow_lower_top'] = (0.5236258282, 3.784966458, 5.341226521) points['shadow_upper_bottom'] = (0.5236258282, 7.323868662, 5.341226521) points['shadow_upper_top'] = (0.5236258282, 11.08870881, 5.341226521) -points['spawn1'] = (-4.265431979, 5.461124528, - -4.003708602) + (0.4063164993, 1.0, 2.195980213) -points['spawn2'] = (5.073366552, 5.444726373, - -4.063095718) + (0.3465292314, 1.0, 2.221665995) -points['spawn_by_flag1'] = (-6.663464996, 5.978624661, - -6.165495294) + (0.7518730647, 1.0, 0.8453811633) -points['spawn_by_flag2'] = (7.389476227, 5.978624661, - -1.709266011) + (0.7518730647, 1.0, 0.8453811633) -points['spawn_by_flag3'] = (-2.112456453, 4.802744618, - -3.947702091) + (0.7518730647, 1.0, 0.8453811633) -points['spawn_by_flag4'] = (2.701146355, 4.802744618, - -3.947702091) + (0.7518730647, 1.0, 0.8453811633) +points['spawn1'] = (-4.265431979, 5.461124528, -4.003708602) + ( + 0.4063164993, + 1.0, + 2.195980213, +) +points['spawn2'] = (5.073366552, 5.444726373, -4.063095718) + ( + 0.3465292314, + 1.0, + 2.221665995, +) +points['spawn_by_flag1'] = (-6.663464996, 5.978624661, -6.165495294) + ( + 0.7518730647, + 1.0, + 0.8453811633, +) +points['spawn_by_flag2'] = (7.389476227, 5.978624661, -1.709266011) + ( + 0.7518730647, + 1.0, + 0.8453811633, +) +points['spawn_by_flag3'] = (-2.112456453, 4.802744618, -3.947702091) + ( + 0.7518730647, + 1.0, + 0.8453811633, +) +points['spawn_by_flag4'] = (2.701146355, 4.802744618, -3.947702091) + ( + 0.7518730647, + 1.0, + 0.8453811633, +) points['tnt1'] = (0.258764453, 4.834253071, -4.306874943) diff --git a/dist/ba_data/python/bastd/mapdata/the_pad.py b/dist/ba_data/python/bastd/mapdata/the_pad.py index 7afe629..cfdc835 100644 --- a/dist/ba_data/python/bastd/mapdata/the_pad.py +++ b/dist/ba_data/python/bastd/mapdata/the_pad.py @@ -6,22 +6,39 @@ points = {} # noinspection PyDictCreation boxes = {} -boxes['area_of_interest_bounds'] = (0.3544110667, 4.493562578, - -2.518391331) + (0.0, 0.0, 0.0) + ( - 16.64754831, 8.06138989, 18.5029888) -points['ffa_spawn1'] = (-3.812275836, 4.380655495, - -8.962074979) + (2.371946621, 1.0, 0.8737798622) -points['ffa_spawn2'] = (4.472503025, 4.406820459, - -9.007239732) + (2.708525168, 1.0, 0.8737798622) -points['ffa_spawn3'] = (6.972673935, 4.380775486, - -7.424407061) + (0.4850648533, 1.0, 1.597018665) -points['ffa_spawn4'] = (-6.36978974, 4.380775486, - -7.424407061) + (0.4850648533, 1.0, 1.597018665) +boxes['area_of_interest_bounds'] = ( + (0.3544110667, 4.493562578, -2.518391331) + + (0.0, 0.0, 0.0) + + (16.64754831, 8.06138989, 18.5029888) +) +points['ffa_spawn1'] = (-3.812275836, 4.380655495, -8.962074979) + ( + 2.371946621, + 1.0, + 0.8737798622, +) +points['ffa_spawn2'] = (4.472503025, 4.406820459, -9.007239732) + ( + 2.708525168, + 1.0, + 0.8737798622, +) +points['ffa_spawn3'] = (6.972673935, 4.380775486, -7.424407061) + ( + 0.4850648533, + 1.0, + 1.597018665, +) +points['ffa_spawn4'] = (-6.36978974, 4.380775486, -7.424407061) + ( + 0.4850648533, + 1.0, + 1.597018665, +) points['flag1'] = (-7.026110145, 4.308759233, -6.302807727) points['flag2'] = (7.632557137, 4.366002373, -6.287969342) points['flag_default'] = (0.4611826686, 4.382076338, 3.680881802) -boxes['map_bounds'] = (0.2608783669, 4.899663734, -3.543675157) + ( - 0.0, 0.0, 0.0) + (29.23565494, 14.19991443, 29.92689344) +boxes['map_bounds'] = ( + (0.2608783669, 4.899663734, -3.543675157) + + (0.0, 0.0, 0.0) + + (29.23565494, 14.19991443, 29.92689344) +) points['powerup_spawn1'] = (-4.166594349, 5.281834349, -6.427493781) points['powerup_spawn2'] = (4.426873526, 5.342460464, -6.329745237) points['powerup_spawn3'] = (-4.201686731, 5.123385835, 0.4400721376) @@ -30,8 +47,14 @@ points['shadow_lower_bottom'] = (-0.2912522507, 2.020798381, 5.341226521) points['shadow_lower_top'] = (-0.2912522507, 3.206066063, 5.341226521) points['shadow_upper_bottom'] = (-0.2912522507, 6.062361813, 5.341226521) points['shadow_upper_top'] = (-0.2912522507, 9.827201965, 5.341226521) -points['spawn1'] = (-3.902942148, 4.380655495, - -8.962074979) + (1.66339533, 1.0, 0.8737798622) -points['spawn2'] = (4.775040345, 4.406820459, -9.007239732) + (1.66339533, 1.0, - 0.8737798622) +points['spawn1'] = (-3.902942148, 4.380655495, -8.962074979) + ( + 1.66339533, + 1.0, + 0.8737798622, +) +points['spawn2'] = (4.775040345, 4.406820459, -9.007239732) + ( + 1.66339533, + 1.0, + 0.8737798622, +) points['tnt1'] = (0.4599593402, 4.044276501, -6.573537395) diff --git a/dist/ba_data/python/bastd/mapdata/tip_top.py b/dist/ba_data/python/bastd/mapdata/tip_top.py index 7e8a17d..19c9326 100644 --- a/dist/ba_data/python/bastd/mapdata/tip_top.py +++ b/dist/ba_data/python/bastd/mapdata/tip_top.py @@ -6,28 +6,54 @@ points = {} # noinspection PyDictCreation boxes = {} -boxes['area_of_interest_bounds'] = (0.004375512593, 7.141135803, - -0.01745294675) + (0.0, 0.0, 0.0) + ( - 21.12506141, 4.959977313, 16.6885592) +boxes['area_of_interest_bounds'] = ( + (0.004375512593, 7.141135803, -0.01745294675) + + (0.0, 0.0, 0.0) + + (21.12506141, 4.959977313, 16.6885592) +) points['ffa_spawn1'] = (-4.211611443, 6.96684623, -3.792009469) + ( - 0.3862608373, 1.155037873, 0.301362335) -points['ffa_spawn2'] = (7.384331793, 5.212769921, - -2.788130319) + (1.155037873, 1.155037873, 1.155037873) -points['ffa_spawn3'] = (-7.264053816, 5.461241477, - -3.095884089) + (1.155037873, 1.155037873, 1.155037873) -points['ffa_spawn4'] = (0.02413253541, 5.367227206, - 4.075190968) + (1.875050182, 1.155037873, 0.2019443553) + 0.3862608373, + 1.155037873, + 0.301362335, +) +points['ffa_spawn2'] = (7.384331793, 5.212769921, -2.788130319) + ( + 1.155037873, + 1.155037873, + 1.155037873, +) +points['ffa_spawn3'] = (-7.264053816, 5.461241477, -3.095884089) + ( + 1.155037873, + 1.155037873, + 1.155037873, +) +points['ffa_spawn4'] = (0.02413253541, 5.367227206, 4.075190968) + ( + 1.875050182, + 1.155037873, + 0.2019443553, +) points['ffa_spawn5'] = (-1.571185756, 7.042332385, -0.4760548825) + ( - 0.3862608373, 1.155037873, 0.301362335) + 0.3862608373, + 1.155037873, + 0.301362335, +) points['ffa_spawn6'] = (1.693597207, 7.042332385, -0.4760548825) + ( - 0.3862608373, 1.155037873, 0.301362335) + 0.3862608373, + 1.155037873, + 0.301362335, +) points['ffa_spawn7'] = (4.398059102, 6.96684623, -3.802802846) + ( - 0.3862608373, 1.155037873, 0.301362335) + 0.3862608373, + 1.155037873, + 0.301362335, +) points['flag1'] = (-7.006685836, 5.420897881, -2.717154638) points['flag2'] = (7.166003893, 5.166226103, -2.651234621) points['flag_default'] = (0.07287520555, 8.865234972, -4.988876512) -boxes['map_bounds'] = (-0.2103025678, 7.746661892, -0.3767425594) + ( - 0.0, 0.0, 0.0) + (23.8148841, 13.86473252, 16.37749544) +boxes['map_bounds'] = ( + (-0.2103025678, 7.746661892, -0.3767425594) + + (0.0, 0.0, 0.0) + + (23.8148841, 13.86473252, 16.37749544) +) points['powerup_spawn1'] = (1.660037213, 8.050002248, -1.221221367) points['powerup_spawn2'] = (-1.486576666, 7.912313704, -1.233393956) points['powerup_spawn3'] = (2.629546191, 6.361794487, 1.399066775) @@ -36,7 +62,13 @@ points['shadow_lower_bottom'] = (0.07287520555, 4.000760229, 6.31658856) points['shadow_lower_top'] = (0.07287520555, 4.751182548, 6.31658856) points['shadow_upper_bottom'] = (0.07287520555, 9.154987885, 6.31658856) points['shadow_upper_top'] = (0.07287520555, 13.8166857, 6.31658856) -points['spawn1'] = (-7.264053816, 5.461241477, - -3.095884089) + (1.155037873, 1.155037873, 1.155037873) -points['spawn2'] = (7.384331793, 5.212769921, - -2.788130319) + (1.155037873, 1.155037873, 1.155037873) +points['spawn1'] = (-7.264053816, 5.461241477, -3.095884089) + ( + 1.155037873, + 1.155037873, + 1.155037873, +) +points['spawn2'] = (7.384331793, 5.212769921, -2.788130319) + ( + 1.155037873, + 1.155037873, + 1.155037873, +) diff --git a/dist/ba_data/python/bastd/mapdata/tower_d.py b/dist/ba_data/python/bastd/mapdata/tower_d.py index bc7b435..0816c2b 100644 --- a/dist/ba_data/python/bastd/mapdata/tower_d.py +++ b/dist/ba_data/python/bastd/mapdata/tower_d.py @@ -6,58 +6,120 @@ points = {} # noinspection PyDictCreation boxes = {} -boxes['area_of_interest_bounds'] = (-0.4714933293, 2.887077774, - -1.505479919) + (0.0, 0.0, 0.0) + ( - 17.90145968, 6.188484831, 15.96149117) -boxes['b1'] = (-4.832680224, 2.581977222, -2.345017289) + (0.0, 0.0, 0.0) + ( - 0.9362069954, 2.750877211, 7.267441751) -boxes['b2'] = (4.74117666, 2.581977222, -3.341802598) + (0.0, 0.0, 0.0) + ( - 0.8162971446, 2.750877211, 7.222259865) -boxes['b3'] = (6.423991249, 2.581977222, -4.423628975) + (0.0, 0.0, 0.0) + ( - 0.8263776825, 4.534503966, 9.174814862) -boxes['b4'] = (-6.647568724, 3.229920028, -3.057507544) + (0.0, 0.0, 0.0) + ( - 0.9362069954, 2.750877211, 8.925082868) -boxes['b5'] = (3.537030094, 2.581977222, -2.366599524) + (0.0, 0.0, 0.0) + ( - 0.6331341496, 2.750877211, 5.327279595) -boxes['b6'] = (5.758481164, 2.581977222, 1.147676334) + (0.0, 0.0, 0.0) + ( - 2.300443227, 2.182113263, 0.5026852656) -boxes['b7'] = (-2.872805708, 2.581977222, -1.563390778) + (0.0, 0.0, 0.0) + ( - 0.9362069954, 2.750877211, 5.916439874) -boxes['b8'] = (-0.6884909563, 4.756572255, -5.317308535) + (0.0, 0.0, 0.0) + ( - 24.86081756, 9.001418153, 13.43159632) -boxes['b9'] = (-0.01526615369, 2.860860149, -0.9527080851) + ( - 0.0, 0.0, 0.0) + (4.899031047, 3.041987448, 6.142004808) +boxes['area_of_interest_bounds'] = ( + (-0.4714933293, 2.887077774, -1.505479919) + + (0.0, 0.0, 0.0) + + (17.90145968, 6.188484831, 15.96149117) +) +boxes['b1'] = ( + (-4.832680224, 2.581977222, -2.345017289) + + (0.0, 0.0, 0.0) + + (0.9362069954, 2.750877211, 7.267441751) +) +boxes['b2'] = ( + (4.74117666, 2.581977222, -3.341802598) + + (0.0, 0.0, 0.0) + + (0.8162971446, 2.750877211, 7.222259865) +) +boxes['b3'] = ( + (6.423991249, 2.581977222, -4.423628975) + + (0.0, 0.0, 0.0) + + (0.8263776825, 4.534503966, 9.174814862) +) +boxes['b4'] = ( + (-6.647568724, 3.229920028, -3.057507544) + + (0.0, 0.0, 0.0) + + (0.9362069954, 2.750877211, 8.925082868) +) +boxes['b5'] = ( + (3.537030094, 2.581977222, -2.366599524) + + (0.0, 0.0, 0.0) + + (0.6331341496, 2.750877211, 5.327279595) +) +boxes['b6'] = ( + (5.758481164, 2.581977222, 1.147676334) + + (0.0, 0.0, 0.0) + + (2.300443227, 2.182113263, 0.5026852656) +) +boxes['b7'] = ( + (-2.872805708, 2.581977222, -1.563390778) + + (0.0, 0.0, 0.0) + + (0.9362069954, 2.750877211, 5.916439874) +) +boxes['b8'] = ( + (-0.6884909563, 4.756572255, -5.317308535) + + (0.0, 0.0, 0.0) + + (24.86081756, 9.001418153, 13.43159632) +) +boxes['b9'] = ( + (-0.01526615369, 2.860860149, -0.9527080851) + + (0.0, 0.0, 0.0) + + (4.899031047, 3.041987448, 6.142004808) +) points['bot_spawn_bottom_left'] = (-7.400801881, 1.617640411, 5.384327397) + ( - 1.66339533, 1.0, 0.8737798622) + 1.66339533, + 1.0, + 0.8737798622, +) points['bot_spawn_bottom_right'] = (6.492270514, 1.617640411, 5.384327397) + ( - 1.66339533, 1.0, 0.8737798622) -points['bot_spawn_start'] = (-9.000552706, 3.1524, - 0.3095359717) + (1.66339533, 1.0, 0.8737798622) -boxes['edge_box'] = (-0.6502137344, 3.189969807, 4.099657551) + ( - 0.0, 0.0, 0.0) + (14.24474785, 1.469696777, 2.898807504) -boxes['edge_box2'] = (-0.144434237, 2.297109431, 0.3535332953) + ( - 0.0, 0.0, 0.0) + (1.744002325, 1.060637489, 4.905560105) -points['ffa_spawn1'] = (0.1024602894, 2.713599022, - -0.3639688715) + (1.66339533, 1.0, 0.8737798622) + 1.66339533, + 1.0, + 0.8737798622, +) +points['bot_spawn_start'] = (-9.000552706, 3.1524, 0.3095359717) + ( + 1.66339533, + 1.0, + 0.8737798622, +) +boxes['edge_box'] = ( + (-0.6502137344, 3.189969807, 4.099657551) + + (0.0, 0.0, 0.0) + + (14.24474785, 1.469696777, 2.898807504) +) +boxes['edge_box2'] = ( + (-0.144434237, 2.297109431, 0.3535332953) + + (0.0, 0.0, 0.0) + + (1.744002325, 1.060637489, 4.905560105) +) +points['ffa_spawn1'] = (0.1024602894, 2.713599022, -0.3639688715) + ( + 1.66339533, + 1.0, + 0.8737798622, +) points['flag1'] = (-7.751267587, 3.143417127, 0.266009523) points['flag2'] = (6.851493725, 2.251048381, 0.3832888796) points['flag_default'] = (0.01935110284, 2.227757733, -6.312658764) -boxes['map_bounds'] = (0.2608783669, 4.899663734, -3.543675157) + ( - 0.0, 0.0, 0.0) + (29.23565494, 14.19991443, 29.92689344) -boxes['powerup_region'] = (0.3544110667, 4.036899334, 3.413130338) + ( - 0.0, 0.0, 0.0) + (12.48542377, 0.3837028864, 1.622880843) +boxes['map_bounds'] = ( + (0.2608783669, 4.899663734, -3.543675157) + + (0.0, 0.0, 0.0) + + (29.23565494, 14.19991443, 29.92689344) +) +boxes['powerup_region'] = ( + (0.3544110667, 4.036899334, 3.413130338) + + (0.0, 0.0, 0.0) + + (12.48542377, 0.3837028864, 1.622880843) +) points['powerup_spawn1'] = (-4.926642821, 2.397645612, 2.876782366) points['powerup_spawn2'] = (-1.964335546, 2.397645612, 3.751374716) points['powerup_spawn3'] = (1.64883201, 2.397645612, 3.751374716) points['powerup_spawn4'] = (4.398865293, 2.397645612, 2.877618924) -boxes['score_region'] = (8.378672831, 3.049210364, 0.5079393073) + ( - 0.0, 0.0, 0.0) + (1.276586051, 1.368127109, 0.6915290191) +boxes['score_region'] = ( + (8.378672831, 3.049210364, 0.5079393073) + + (0.0, 0.0, 0.0) + + (1.276586051, 1.368127109, 0.6915290191) +) points['shadow_lower_bottom'] = (-0.2912522507, 0.9466821599, 5.341226521) points['shadow_lower_top'] = (-0.2912522507, 2.131949842, 5.341226521) points['shadow_upper_bottom'] = (-0.4529958189, 6.062361813, 5.341226521) points['shadow_upper_top'] = (-0.2912522507, 9.827201965, 5.341226521) -points['spawn1'] = (0.1024602894, 2.713599022, - -0.3639688715) + (1.66339533, 1.0, 0.8737798622) -points['spawn2'] = (0.1449413466, 2.739700702, - -0.3431945526) + (1.66339533, 1.0, 0.8737798622) +points['spawn1'] = (0.1024602894, 2.713599022, -0.3639688715) + ( + 1.66339533, + 1.0, + 0.8737798622, +) +points['spawn2'] = (0.1449413466, 2.739700702, -0.3431945526) + ( + 1.66339533, + 1.0, + 0.8737798622, +) points['tnt_loc'] = (0.008297003631, 2.777570118, 3.894548697) diff --git a/dist/ba_data/python/bastd/mapdata/zig_zag.py b/dist/ba_data/python/bastd/mapdata/zig_zag.py index 701d9f8..51fec1a 100644 --- a/dist/ba_data/python/bastd/mapdata/zig_zag.py +++ b/dist/ba_data/python/bastd/mapdata/zig_zag.py @@ -6,23 +6,41 @@ points = {} # noinspection PyDictCreation boxes = {} -boxes['area_of_interest_bounds'] = (-1.807378035, 3.943412768, -1.61304303) + ( - 0.0, 0.0, 0.0) + (23.01413538, 13.27980464, 10.0098376) -points['ffa_spawn1'] = (-9.523537347, 4.645005984, - -3.193868606) + (0.8511546708, 1.0, 1.303629055) -points['ffa_spawn2'] = (6.217011718, 4.632396767, - -3.190279407) + (0.8844870264, 1.0, 1.303629055) -points['ffa_spawn3'] = (-4.433947713, 3.005988298, - -4.908942678) + (1.531254794, 1.0, 0.7550370795) -points['ffa_spawn4'] = (1.457496709, 3.01461103, - -4.907036892) + (1.531254794, 1.0, 0.7550370795) +boxes['area_of_interest_bounds'] = ( + (-1.807378035, 3.943412768, -1.61304303) + + (0.0, 0.0, 0.0) + + (23.01413538, 13.27980464, 10.0098376) +) +points['ffa_spawn1'] = (-9.523537347, 4.645005984, -3.193868606) + ( + 0.8511546708, + 1.0, + 1.303629055, +) +points['ffa_spawn2'] = (6.217011718, 4.632396767, -3.190279407) + ( + 0.8844870264, + 1.0, + 1.303629055, +) +points['ffa_spawn3'] = (-4.433947713, 3.005988298, -4.908942678) + ( + 1.531254794, + 1.0, + 0.7550370795, +) +points['ffa_spawn4'] = (1.457496709, 3.01461103, -4.907036892) + ( + 1.531254794, + 1.0, + 0.7550370795, +) points['flag1'] = (-9.969465388, 4.651689505, -4.965994534) points['flag2'] = (6.844972512, 4.652142027, -4.951156148) points['flag3'] = (1.155692239, 2.973774873, -4.890524808) points['flag4'] = (-4.224099354, 3.006208239, -4.890524808) points['flag_default'] = (-1.42651413, 3.018804771, 0.8808626995) -boxes['map_bounds'] = (-1.567840276, 8.764115729, -1.311948055) + ( - 0.0, 0.0, 0.0) + (28.76792809, 17.64963076, 19.52220249) +boxes['map_bounds'] = ( + (-1.567840276, 8.764115729, -1.311948055) + + (0.0, 0.0, 0.0) + + (28.76792809, 17.64963076, 19.52220249) +) points['powerup_spawn1'] = (2.55551802, 4.369175386, -4.798598276) points['powerup_spawn2'] = (-6.02484656, 4.369175386, -4.798598276) points['powerup_spawn3'] = (5.557525662, 5.378865401, -4.798598276) @@ -31,16 +49,34 @@ points['shadow_lower_bottom'] = (-1.42651413, 1.681630068, 4.790029929) points['shadow_lower_top'] = (-1.42651413, 2.54918356, 4.790029929) points['shadow_upper_bottom'] = (-1.42651413, 6.802574329, 4.790029929) points['shadow_upper_top'] = (-1.42651413, 8.779767257, 4.790029929) -points['spawn1'] = (-9.523537347, 4.645005984, - -3.193868606) + (0.8511546708, 1.0, 1.303629055) -points['spawn2'] = (6.217011718, 4.632396767, - -3.190279407) + (0.8844870264, 1.0, 1.303629055) -points['spawn_by_flag1'] = (-9.523537347, 4.645005984, - -3.193868606) + (0.8511546708, 1.0, 1.303629055) -points['spawn_by_flag2'] = (6.217011718, 4.632396767, - -3.190279407) + (0.8844870264, 1.0, 1.303629055) -points['spawn_by_flag3'] = (1.457496709, 3.01461103, - -4.907036892) + (1.531254794, 1.0, 0.7550370795) -points['spawn_by_flag4'] = (-4.433947713, 3.005988298, - -4.908942678) + (1.531254794, 1.0, 0.7550370795) +points['spawn1'] = (-9.523537347, 4.645005984, -3.193868606) + ( + 0.8511546708, + 1.0, + 1.303629055, +) +points['spawn2'] = (6.217011718, 4.632396767, -3.190279407) + ( + 0.8844870264, + 1.0, + 1.303629055, +) +points['spawn_by_flag1'] = (-9.523537347, 4.645005984, -3.193868606) + ( + 0.8511546708, + 1.0, + 1.303629055, +) +points['spawn_by_flag2'] = (6.217011718, 4.632396767, -3.190279407) + ( + 0.8844870264, + 1.0, + 1.303629055, +) +points['spawn_by_flag3'] = (1.457496709, 3.01461103, -4.907036892) + ( + 1.531254794, + 1.0, + 0.7550370795, +) +points['spawn_by_flag4'] = (-4.433947713, 3.005988298, -4.908942678) + ( + 1.531254794, + 1.0, + 0.7550370795, +) points['tnt1'] = (-1.42651413, 4.045239665, 0.04094631341) diff --git a/dist/ba_data/python/bastd/maps.py b/dist/ba_data/python/bastd/maps.py index 7d3779e..c20c82a 100644 --- a/dist/ba_data/python/bastd/maps.py +++ b/dist/ba_data/python/bastd/maps.py @@ -18,6 +18,7 @@ class HockeyStadium(ba.Map): """Stadium map used for ice hockey games.""" from bastd.mapdata import hockey_stadium as defs + name = 'Hockey Stadium' @classmethod @@ -32,13 +33,15 @@ class HockeyStadium(ba.Map): @classmethod def on_preload(cls) -> Any: data: dict[str, Any] = { - 'models': (ba.getmodel('hockeyStadiumOuter'), - ba.getmodel('hockeyStadiumInner'), - ba.getmodel('hockeyStadiumStands')), + 'models': ( + ba.getmodel('hockeyStadiumOuter'), + ba.getmodel('hockeyStadiumInner'), + ba.getmodel('hockeyStadiumStands'), + ), 'vr_fill_model': ba.getmodel('footballStadiumVRFill'), 'collide_model': ba.getcollidemodel('hockeyStadiumCollide'), 'tex': ba.gettexture('hockeyStadium'), - 'stands_tex': ba.gettexture('footballStadium') + 'stands_tex': ba.gettexture('footballStadium'), } mat = ba.Material() mat.add_actions(actions=('modify_part_collision', 'friction', 0.01)) @@ -48,44 +51,48 @@ class HockeyStadium(ba.Map): def __init__(self) -> None: super().__init__() shared = SharedObjects.get() - self.node = ba.newnode('terrain', - delegate=self, - attrs={ - 'model': - self.preloaddata['models'][0], - 'collide_model': - self.preloaddata['collide_model'], - 'color_texture': - self.preloaddata['tex'], - 'materials': [ - shared.footing_material, - self.preloaddata['ice_material'] - ] - }) - ba.newnode('terrain', - attrs={ - 'model': self.preloaddata['vr_fill_model'], - 'vr_only': True, - 'lighting': False, - 'background': True, - 'color_texture': self.preloaddata['stands_tex'] - }) + self.node = ba.newnode( + 'terrain', + delegate=self, + attrs={ + 'model': self.preloaddata['models'][0], + 'collide_model': self.preloaddata['collide_model'], + 'color_texture': self.preloaddata['tex'], + 'materials': [ + shared.footing_material, + self.preloaddata['ice_material'], + ], + }, + ) + ba.newnode( + 'terrain', + attrs={ + 'model': self.preloaddata['vr_fill_model'], + 'vr_only': True, + 'lighting': False, + 'background': True, + 'color_texture': self.preloaddata['stands_tex'], + }, + ) mats = [shared.footing_material, self.preloaddata['ice_material']] - self.floor = ba.newnode('terrain', - attrs={ - 'model': self.preloaddata['models'][1], - 'color_texture': self.preloaddata['tex'], - 'opacity': 0.92, - 'opacity_in_low_or_medium_quality': 1.0, - 'materials': mats - }) + self.floor = ba.newnode( + 'terrain', + attrs={ + 'model': self.preloaddata['models'][1], + 'color_texture': self.preloaddata['tex'], + 'opacity': 0.92, + 'opacity_in_low_or_medium_quality': 1.0, + 'materials': mats, + }, + ) self.stands = ba.newnode( 'terrain', attrs={ 'model': self.preloaddata['models'][2], 'visible_in_reflections': False, - 'color_texture': self.preloaddata['stands_tex'] - }) + 'color_texture': self.preloaddata['stands_tex'], + }, + ) gnode = ba.getactivity().globalsnode gnode.floor_reflection = True gnode.debris_friction = 0.3 @@ -101,6 +108,7 @@ class HockeyStadium(ba.Map): class FootballStadium(ba.Map): """Stadium map for football games.""" + from bastd.mapdata import football_stadium as defs name = 'Football Stadium' @@ -120,7 +128,7 @@ class FootballStadium(ba.Map): 'model': ba.getmodel('footballStadium'), 'vr_fill_model': ba.getmodel('footballStadiumVRFill'), 'collide_model': ba.getcollidemodel('footballStadiumCollide'), - 'tex': ba.gettexture('footballStadium') + 'tex': ba.gettexture('footballStadium'), } return data @@ -134,16 +142,19 @@ class FootballStadium(ba.Map): 'model': self.preloaddata['model'], 'collide_model': self.preloaddata['collide_model'], 'color_texture': self.preloaddata['tex'], - 'materials': [shared.footing_material] - }) - ba.newnode('terrain', - attrs={ - 'model': self.preloaddata['vr_fill_model'], - 'lighting': False, - 'vr_only': True, - 'background': True, - 'color_texture': self.preloaddata['tex'] - }) + 'materials': [shared.footing_material], + }, + ) + ba.newnode( + 'terrain', + attrs={ + 'model': self.preloaddata['vr_fill_model'], + 'lighting': False, + 'vr_only': True, + 'background': True, + 'color_texture': self.preloaddata['tex'], + }, + ) gnode = ba.getactivity().globalsnode gnode.tint = (1.3, 1.2, 1.0) gnode.ambient_color = (1.3, 1.2, 1.0) @@ -152,9 +163,7 @@ class FootballStadium(ba.Map): gnode.vr_camera_offset = (0, -0.8, -1.1) gnode.vr_near_clip = 0.5 - def is_point_near_edge(self, - point: ba.Vec3, - running: bool = False) -> bool: + def is_point_near_edge(self, point: ba.Vec3, running: bool = False) -> bool: box_position = self.defs.boxes['edge_box'][0:3] box_scale = self.defs.boxes['edge_box'][6:9] xpos = (point.x - box_position[0]) / box_scale[0] @@ -164,6 +173,7 @@ class FootballStadium(ba.Map): class Bridgit(ba.Map): """Map with a narrow bridge in the middle.""" + from bastd.mapdata import bridgit as defs name = 'Bridgit' @@ -190,12 +200,14 @@ class Bridgit(ba.Map): 'tex': ba.gettexture('bridgitLevelColor'), 'model_bg_tex': ba.gettexture('natureBackgroundColor'), 'collide_bg': ba.getcollidemodel('natureBackgroundCollide'), - 'railing_collide_model': - (ba.getcollidemodel('bridgitLevelRailingCollide')), - 'bg_material': ba.Material() + 'railing_collide_model': ( + ba.getcollidemodel('bridgitLevelRailingCollide') + ), + 'bg_material': ba.Material(), } - data['bg_material'].add_actions(actions=('modify_part_collision', - 'friction', 10.0)) + data['bg_material'].add_actions( + actions=('modify_part_collision', 'friction', 10.0) + ) return data def __init__(self) -> None: @@ -208,47 +220,55 @@ class Bridgit(ba.Map): 'collide_model': self.preloaddata['collide_model'], 'model': self.preloaddata['model_top'], 'color_texture': self.preloaddata['tex'], - 'materials': [shared.footing_material] - }) - self.bottom = ba.newnode('terrain', - attrs={ - 'model': self.preloaddata['model_bottom'], - 'lighting': False, - 'color_texture': self.preloaddata['tex'] - }) + 'materials': [shared.footing_material], + }, + ) + self.bottom = ba.newnode( + 'terrain', + attrs={ + 'model': self.preloaddata['model_bottom'], + 'lighting': False, + 'color_texture': self.preloaddata['tex'], + }, + ) self.background = ba.newnode( 'terrain', attrs={ 'model': self.preloaddata['model_bg'], 'lighting': False, 'background': True, - 'color_texture': self.preloaddata['model_bg_tex'] - }) - ba.newnode('terrain', - attrs={ - 'model': self.preloaddata['bg_vr_fill_model'], - 'lighting': False, - 'vr_only': True, - 'background': True, - 'color_texture': self.preloaddata['model_bg_tex'] - }) + 'color_texture': self.preloaddata['model_bg_tex'], + }, + ) + ba.newnode( + 'terrain', + attrs={ + 'model': self.preloaddata['bg_vr_fill_model'], + 'lighting': False, + 'vr_only': True, + 'background': True, + 'color_texture': self.preloaddata['model_bg_tex'], + }, + ) self.railing = ba.newnode( 'terrain', attrs={ 'collide_model': self.preloaddata['railing_collide_model'], 'materials': [shared.railing_material], - 'bumper': True - }) - self.bg_collide = ba.newnode('terrain', - attrs={ - 'collide_model': - self.preloaddata['collide_bg'], - 'materials': [ - shared.footing_material, - self.preloaddata['bg_material'], - shared.death_material - ] - }) + 'bumper': True, + }, + ) + self.bg_collide = ba.newnode( + 'terrain', + attrs={ + 'collide_model': self.preloaddata['collide_bg'], + 'materials': [ + shared.footing_material, + self.preloaddata['bg_material'], + shared.death_material, + ], + }, + ) gnode = ba.getactivity().globalsnode gnode.tint = (1.1, 1.2, 1.3) gnode.ambient_color = (1.1, 1.2, 1.3) @@ -267,8 +287,12 @@ class BigG(ba.Map): def get_play_types(cls) -> list[str]: """Return valid play types for this map.""" return [ - 'race', 'melee', 'keep_away', 'team_flag', 'king_of_the_hill', - 'conquest' + 'race', + 'melee', + 'keep_away', + 'team_flag', + 'king_of_the_hill', + 'conquest', ] @classmethod @@ -287,10 +311,11 @@ class BigG(ba.Map): 'model_bg_tex': ba.gettexture('natureBackgroundColor'), 'collide_bg': ba.getcollidemodel('natureBackgroundCollide'), 'bumper_collide_model': ba.getcollidemodel('bigGBumper'), - 'bg_material': ba.Material() + 'bg_material': ba.Material(), } - data['bg_material'].add_actions(actions=('modify_part_collision', - 'friction', 10.0)) + data['bg_material'].add_actions( + actions=('modify_part_collision', 'friction', 10.0) + ) return data def __init__(self) -> None: @@ -304,48 +329,56 @@ class BigG(ba.Map): 'color': (0.7, 0.7, 0.7), 'model': self.preloaddata['model_top'], 'color_texture': self.preloaddata['tex'], - 'materials': [shared.footing_material] - }) - self.bottom = ba.newnode('terrain', - attrs={ - 'model': self.preloaddata['model_bottom'], - 'color': (0.7, 0.7, 0.7), - 'lighting': False, - 'color_texture': self.preloaddata['tex'] - }) + 'materials': [shared.footing_material], + }, + ) + self.bottom = ba.newnode( + 'terrain', + attrs={ + 'model': self.preloaddata['model_bottom'], + 'color': (0.7, 0.7, 0.7), + 'lighting': False, + 'color_texture': self.preloaddata['tex'], + }, + ) self.background = ba.newnode( 'terrain', attrs={ 'model': self.preloaddata['model_bg'], 'lighting': False, 'background': True, - 'color_texture': self.preloaddata['model_bg_tex'] - }) - ba.newnode('terrain', - attrs={ - 'model': self.preloaddata['bg_vr_fill_model'], - 'lighting': False, - 'vr_only': True, - 'background': True, - 'color_texture': self.preloaddata['model_bg_tex'] - }) + 'color_texture': self.preloaddata['model_bg_tex'], + }, + ) + ba.newnode( + 'terrain', + attrs={ + 'model': self.preloaddata['bg_vr_fill_model'], + 'lighting': False, + 'vr_only': True, + 'background': True, + 'color_texture': self.preloaddata['model_bg_tex'], + }, + ) self.railing = ba.newnode( 'terrain', attrs={ 'collide_model': self.preloaddata['bumper_collide_model'], 'materials': [shared.railing_material], - 'bumper': True - }) - self.bg_collide = ba.newnode('terrain', - attrs={ - 'collide_model': - self.preloaddata['collide_bg'], - 'materials': [ - shared.footing_material, - self.preloaddata['bg_material'], - shared.death_material - ] - }) + 'bumper': True, + }, + ) + self.bg_collide = ba.newnode( + 'terrain', + attrs={ + 'collide_model': self.preloaddata['collide_bg'], + 'materials': [ + shared.footing_material, + self.preloaddata['bg_material'], + shared.death_material, + ], + }, + ) gnode = ba.getactivity().globalsnode gnode.tint = (1.1, 1.2, 1.3) gnode.ambient_color = (1.1, 1.2, 1.3) @@ -380,12 +413,14 @@ class Roundabout(ba.Map): 'tex': ba.gettexture('roundaboutLevelColor'), 'model_bg_tex': ba.gettexture('natureBackgroundColor'), 'collide_bg': ba.getcollidemodel('natureBackgroundCollide'), - 'railing_collide_model': - (ba.getcollidemodel('roundaboutLevelBumper')), - 'bg_material': ba.Material() + 'railing_collide_model': ( + ba.getcollidemodel('roundaboutLevelBumper') + ), + 'bg_material': ba.Material(), } - data['bg_material'].add_actions(actions=('modify_part_collision', - 'friction', 10.0)) + data['bg_material'].add_actions( + actions=('modify_part_collision', 'friction', 10.0) + ) return data def __init__(self) -> None: @@ -398,47 +433,55 @@ class Roundabout(ba.Map): 'collide_model': self.preloaddata['collide_model'], 'model': self.preloaddata['model'], 'color_texture': self.preloaddata['tex'], - 'materials': [shared.footing_material] - }) - self.bottom = ba.newnode('terrain', - attrs={ - 'model': self.preloaddata['model_bottom'], - 'lighting': False, - 'color_texture': self.preloaddata['tex'] - }) + 'materials': [shared.footing_material], + }, + ) + self.bottom = ba.newnode( + 'terrain', + attrs={ + 'model': self.preloaddata['model_bottom'], + 'lighting': False, + 'color_texture': self.preloaddata['tex'], + }, + ) self.background = ba.newnode( 'terrain', attrs={ 'model': self.preloaddata['model_bg'], 'lighting': False, 'background': True, - 'color_texture': self.preloaddata['model_bg_tex'] - }) - ba.newnode('terrain', - attrs={ - 'model': self.preloaddata['bg_vr_fill_model'], - 'lighting': False, - 'vr_only': True, - 'background': True, - 'color_texture': self.preloaddata['model_bg_tex'] - }) - self.bg_collide = ba.newnode('terrain', - attrs={ - 'collide_model': - self.preloaddata['collide_bg'], - 'materials': [ - shared.footing_material, - self.preloaddata['bg_material'], - shared.death_material - ] - }) + 'color_texture': self.preloaddata['model_bg_tex'], + }, + ) + ba.newnode( + 'terrain', + attrs={ + 'model': self.preloaddata['bg_vr_fill_model'], + 'lighting': False, + 'vr_only': True, + 'background': True, + 'color_texture': self.preloaddata['model_bg_tex'], + }, + ) + self.bg_collide = ba.newnode( + 'terrain', + attrs={ + 'collide_model': self.preloaddata['collide_bg'], + 'materials': [ + shared.footing_material, + self.preloaddata['bg_material'], + shared.death_material, + ], + }, + ) self.railing = ba.newnode( 'terrain', attrs={ 'collide_model': self.preloaddata['railing_collide_model'], 'materials': [shared.railing_material], - 'bumper': True - }) + 'bumper': True, + }, + ) gnode = ba.getactivity().globalsnode gnode.tint = (1.0, 1.05, 1.1) gnode.ambient_color = (1.0, 1.05, 1.1) @@ -474,12 +517,14 @@ class MonkeyFace(ba.Map): 'tex': ba.gettexture('monkeyFaceLevelColor'), 'model_bg_tex': ba.gettexture('natureBackgroundColor'), 'collide_bg': ba.getcollidemodel('natureBackgroundCollide'), - 'railing_collide_model': - (ba.getcollidemodel('monkeyFaceLevelBumper')), - 'bg_material': ba.Material() + 'railing_collide_model': ( + ba.getcollidemodel('monkeyFaceLevelBumper') + ), + 'bg_material': ba.Material(), } - data['bg_material'].add_actions(actions=('modify_part_collision', - 'friction', 10.0)) + data['bg_material'].add_actions( + actions=('modify_part_collision', 'friction', 10.0) + ) return data def __init__(self) -> None: @@ -492,47 +537,55 @@ class MonkeyFace(ba.Map): 'collide_model': self.preloaddata['collide_model'], 'model': self.preloaddata['model'], 'color_texture': self.preloaddata['tex'], - 'materials': [shared.footing_material] - }) - self.bottom = ba.newnode('terrain', - attrs={ - 'model': self.preloaddata['bottom_model'], - 'lighting': False, - 'color_texture': self.preloaddata['tex'] - }) + 'materials': [shared.footing_material], + }, + ) + self.bottom = ba.newnode( + 'terrain', + attrs={ + 'model': self.preloaddata['bottom_model'], + 'lighting': False, + 'color_texture': self.preloaddata['tex'], + }, + ) self.background = ba.newnode( 'terrain', attrs={ 'model': self.preloaddata['model_bg'], 'lighting': False, 'background': True, - 'color_texture': self.preloaddata['model_bg_tex'] - }) - ba.newnode('terrain', - attrs={ - 'model': self.preloaddata['bg_vr_fill_model'], - 'lighting': False, - 'vr_only': True, - 'background': True, - 'color_texture': self.preloaddata['model_bg_tex'] - }) - self.bg_collide = ba.newnode('terrain', - attrs={ - 'collide_model': - self.preloaddata['collide_bg'], - 'materials': [ - shared.footing_material, - self.preloaddata['bg_material'], - shared.death_material - ] - }) + 'color_texture': self.preloaddata['model_bg_tex'], + }, + ) + ba.newnode( + 'terrain', + attrs={ + 'model': self.preloaddata['bg_vr_fill_model'], + 'lighting': False, + 'vr_only': True, + 'background': True, + 'color_texture': self.preloaddata['model_bg_tex'], + }, + ) + self.bg_collide = ba.newnode( + 'terrain', + attrs={ + 'collide_model': self.preloaddata['collide_bg'], + 'materials': [ + shared.footing_material, + self.preloaddata['bg_material'], + shared.death_material, + ], + }, + ) self.railing = ba.newnode( 'terrain', attrs={ 'collide_model': self.preloaddata['railing_collide_model'], 'materials': [shared.railing_material], - 'bumper': True - }) + 'bumper': True, + }, + ) gnode = ba.getactivity().globalsnode gnode.tint = (1.1, 1.2, 1.2) gnode.ambient_color = (1.2, 1.3, 1.3) @@ -552,7 +605,11 @@ class ZigZag(ba.Map): def get_play_types(cls) -> list[str]: """Return valid play types for this map.""" return [ - 'melee', 'keep_away', 'team_flag', 'conquest', 'king_of_the_hill' + 'melee', + 'keep_away', + 'team_flag', + 'conquest', + 'king_of_the_hill', ] @classmethod @@ -571,10 +628,11 @@ class ZigZag(ba.Map): 'model_bg_tex': ba.gettexture('natureBackgroundColor'), 'collide_bg': ba.getcollidemodel('natureBackgroundCollide'), 'railing_collide_model': ba.getcollidemodel('zigZagLevelBumper'), - 'bg_material': ba.Material() + 'bg_material': ba.Material(), } - data['bg_material'].add_actions(actions=('modify_part_collision', - 'friction', 10.0)) + data['bg_material'].add_actions( + actions=('modify_part_collision', 'friction', 10.0) + ) return data def __init__(self) -> None: @@ -587,46 +645,54 @@ class ZigZag(ba.Map): 'collide_model': self.preloaddata['collide_model'], 'model': self.preloaddata['model'], 'color_texture': self.preloaddata['tex'], - 'materials': [shared.footing_material] - }) + 'materials': [shared.footing_material], + }, + ) self.background = ba.newnode( 'terrain', attrs={ 'model': self.preloaddata['model_bg'], 'lighting': False, - 'color_texture': self.preloaddata['model_bg_tex'] - }) - self.bottom = ba.newnode('terrain', - attrs={ - 'model': self.preloaddata['model_bottom'], - 'lighting': False, - 'color_texture': self.preloaddata['tex'] - }) - ba.newnode('terrain', - attrs={ - 'model': self.preloaddata['bg_vr_fill_model'], - 'lighting': False, - 'vr_only': True, - 'background': True, - 'color_texture': self.preloaddata['model_bg_tex'] - }) - self.bg_collide = ba.newnode('terrain', - attrs={ - 'collide_model': - self.preloaddata['collide_bg'], - 'materials': [ - shared.footing_material, - self.preloaddata['bg_material'], - shared.death_material - ] - }) + 'color_texture': self.preloaddata['model_bg_tex'], + }, + ) + self.bottom = ba.newnode( + 'terrain', + attrs={ + 'model': self.preloaddata['model_bottom'], + 'lighting': False, + 'color_texture': self.preloaddata['tex'], + }, + ) + ba.newnode( + 'terrain', + attrs={ + 'model': self.preloaddata['bg_vr_fill_model'], + 'lighting': False, + 'vr_only': True, + 'background': True, + 'color_texture': self.preloaddata['model_bg_tex'], + }, + ) + self.bg_collide = ba.newnode( + 'terrain', + attrs={ + 'collide_model': self.preloaddata['collide_bg'], + 'materials': [ + shared.footing_material, + self.preloaddata['bg_material'], + shared.death_material, + ], + }, + ) self.railing = ba.newnode( 'terrain', attrs={ 'collide_model': self.preloaddata['railing_collide_model'], 'materials': [shared.railing_material], - 'bumper': True - }) + 'bumper': True, + }, + ) gnode = ba.getactivity().globalsnode gnode.tint = (1.0, 1.15, 1.15) gnode.ambient_color = (1.0, 1.15, 1.15) @@ -662,7 +728,7 @@ class ThePad(ba.Map): 'bgmodel': ba.getmodel('thePadBG'), 'railing_collide_model': ba.getcollidemodel('thePadLevelBumper'), 'vr_fill_mound_model': ba.getmodel('thePadVRFillMound'), - 'vr_fill_mound_tex': ba.gettexture('vrFillMound') + 'vr_fill_mound_tex': ba.gettexture('vrFillMound'), } # fixme should chop this into vr/non-vr sections for efficiency return data @@ -677,38 +743,45 @@ class ThePad(ba.Map): 'collide_model': self.preloaddata['collide_model'], 'model': self.preloaddata['model'], 'color_texture': self.preloaddata['tex'], - 'materials': [shared.footing_material] - }) - self.bottom = ba.newnode('terrain', - attrs={ - 'model': self.preloaddata['bottom_model'], - 'lighting': False, - 'color_texture': self.preloaddata['tex'] - }) + 'materials': [shared.footing_material], + }, + ) + self.bottom = ba.newnode( + 'terrain', + attrs={ + 'model': self.preloaddata['bottom_model'], + 'lighting': False, + 'color_texture': self.preloaddata['tex'], + }, + ) self.background = ba.newnode( 'terrain', attrs={ 'model': self.preloaddata['bgmodel'], 'lighting': False, 'background': True, - 'color_texture': self.preloaddata['bgtex'] - }) + 'color_texture': self.preloaddata['bgtex'], + }, + ) self.railing = ba.newnode( 'terrain', attrs={ 'collide_model': self.preloaddata['railing_collide_model'], 'materials': [shared.railing_material], - 'bumper': True - }) - ba.newnode('terrain', - attrs={ - 'model': self.preloaddata['vr_fill_mound_model'], - 'lighting': False, - 'vr_only': True, - 'color': (0.56, 0.55, 0.47), - 'background': True, - 'color_texture': self.preloaddata['vr_fill_mound_tex'] - }) + 'bumper': True, + }, + ) + ba.newnode( + 'terrain', + attrs={ + 'model': self.preloaddata['vr_fill_mound_model'], + 'lighting': False, + 'vr_only': True, + 'color': (0.56, 0.55, 0.47), + 'background': True, + 'color_texture': self.preloaddata['vr_fill_mound_tex'], + }, + ) gnode = ba.getactivity().globalsnode gnode.tint = (1.1, 1.1, 1.0) gnode.ambient_color = (1.1, 1.1, 1.0) @@ -742,7 +815,7 @@ class DoomShroom(ba.Map): 'bgmodel': ba.getmodel('doomShroomBG'), 'vr_fill_model': ba.getmodel('doomShroomVRFill'), 'stem_model': ba.getmodel('doomShroomStem'), - 'collide_bg': ba.getcollidemodel('doomShroomStemCollide') + 'collide_bg': ba.getcollidemodel('doomShroomStemCollide'), } return data @@ -756,36 +829,43 @@ class DoomShroom(ba.Map): 'collide_model': self.preloaddata['collide_model'], 'model': self.preloaddata['model'], 'color_texture': self.preloaddata['tex'], - 'materials': [shared.footing_material] - }) + 'materials': [shared.footing_material], + }, + ) self.background = ba.newnode( 'terrain', attrs={ 'model': self.preloaddata['bgmodel'], 'lighting': False, 'background': True, - 'color_texture': self.preloaddata['bgtex'] - }) - ba.newnode('terrain', - attrs={ - 'model': self.preloaddata['vr_fill_model'], - 'lighting': False, - 'vr_only': True, - 'background': True, - 'color_texture': self.preloaddata['bgtex'] - }) - self.stem = ba.newnode('terrain', - attrs={ - 'model': self.preloaddata['stem_model'], - 'lighting': False, - 'color_texture': self.preloaddata['tex'] - }) + 'color_texture': self.preloaddata['bgtex'], + }, + ) + ba.newnode( + 'terrain', + attrs={ + 'model': self.preloaddata['vr_fill_model'], + 'lighting': False, + 'vr_only': True, + 'background': True, + 'color_texture': self.preloaddata['bgtex'], + }, + ) + self.stem = ba.newnode( + 'terrain', + attrs={ + 'model': self.preloaddata['stem_model'], + 'lighting': False, + 'color_texture': self.preloaddata['tex'], + }, + ) self.bg_collide = ba.newnode( 'terrain', attrs={ 'collide_model': self.preloaddata['collide_bg'], - 'materials': [shared.footing_material, shared.death_material] - }) + 'materials': [shared.footing_material, shared.death_material], + }, + ) gnode = ba.getactivity().globalsnode gnode.tint = (0.82, 1.10, 1.15) gnode.ambient_color = (0.9, 1.3, 1.1) @@ -793,9 +873,7 @@ class DoomShroom(ba.Map): gnode.vignette_outer = (0.76, 0.76, 0.76) gnode.vignette_inner = (0.95, 0.95, 0.99) - def is_point_near_edge(self, - point: ba.Vec3, - running: bool = False) -> bool: + def is_point_near_edge(self, point: ba.Vec3, running: bool = False) -> bool: xpos = point.x zpos = point.z x_adj = xpos * 0.125 @@ -831,7 +909,7 @@ class LakeFrigid(ba.Map): 'collide_model': ba.getcollidemodel('lakeFrigidCollide'), 'tex': ba.gettexture('lakeFrigid'), 'tex_reflections': ba.gettexture('lakeFrigidReflections'), - 'vr_fill_model': ba.getmodel('lakeFrigidVRFill') + 'vr_fill_model': ba.getmodel('lakeFrigidVRFill'), } mat = ba.Material() mat.add_actions(actions=('modify_part_collision', 'friction', 0.01)) @@ -841,42 +919,47 @@ class LakeFrigid(ba.Map): def __init__(self) -> None: super().__init__() shared = SharedObjects.get() - self.node = ba.newnode('terrain', - delegate=self, - attrs={ - 'collide_model': - self.preloaddata['collide_model'], - 'model': - self.preloaddata['model'], - 'color_texture': - self.preloaddata['tex'], - 'materials': [ - shared.footing_material, - self.preloaddata['ice_material'] - ] - }) - ba.newnode('terrain', - attrs={ - 'model': self.preloaddata['model_top'], - 'lighting': False, - 'color_texture': self.preloaddata['tex'] - }) - ba.newnode('terrain', - attrs={ - 'model': self.preloaddata['model_reflections'], - 'lighting': False, - 'overlay': True, - 'opacity': 0.15, - 'color_texture': self.preloaddata['tex_reflections'] - }) - ba.newnode('terrain', - attrs={ - 'model': self.preloaddata['vr_fill_model'], - 'lighting': False, - 'vr_only': True, - 'background': True, - 'color_texture': self.preloaddata['tex'] - }) + self.node = ba.newnode( + 'terrain', + delegate=self, + attrs={ + 'collide_model': self.preloaddata['collide_model'], + 'model': self.preloaddata['model'], + 'color_texture': self.preloaddata['tex'], + 'materials': [ + shared.footing_material, + self.preloaddata['ice_material'], + ], + }, + ) + ba.newnode( + 'terrain', + attrs={ + 'model': self.preloaddata['model_top'], + 'lighting': False, + 'color_texture': self.preloaddata['tex'], + }, + ) + ba.newnode( + 'terrain', + attrs={ + 'model': self.preloaddata['model_reflections'], + 'lighting': False, + 'overlay': True, + 'opacity': 0.15, + 'color_texture': self.preloaddata['tex_reflections'], + }, + ) + ba.newnode( + 'terrain', + attrs={ + 'model': self.preloaddata['vr_fill_model'], + 'lighting': False, + 'vr_only': True, + 'background': True, + 'color_texture': self.preloaddata['tex'], + }, + ) gnode = ba.getactivity().globalsnode gnode.tint = (1, 1, 1) gnode.ambient_color = (1, 1, 1) @@ -912,7 +995,7 @@ class TipTop(ba.Map): 'tex': ba.gettexture('tipTopLevelColor'), 'bgtex': ba.gettexture('tipTopBGColor'), 'bgmodel': ba.getmodel('tipTopBG'), - 'railing_collide_model': ba.getcollidemodel('tipTopLevelBumper') + 'railing_collide_model': ba.getcollidemodel('tipTopLevelBumper'), } return data @@ -927,15 +1010,18 @@ class TipTop(ba.Map): 'model': self.preloaddata['model'], 'color_texture': self.preloaddata['tex'], 'color': (0.7, 0.7, 0.7), - 'materials': [shared.footing_material] - }) - self.bottom = ba.newnode('terrain', - attrs={ - 'model': self.preloaddata['bottom_model'], - 'lighting': False, - 'color': (0.7, 0.7, 0.7), - 'color_texture': self.preloaddata['tex'] - }) + 'materials': [shared.footing_material], + }, + ) + self.bottom = ba.newnode( + 'terrain', + attrs={ + 'model': self.preloaddata['bottom_model'], + 'lighting': False, + 'color': (0.7, 0.7, 0.7), + 'color_texture': self.preloaddata['tex'], + }, + ) self.background = ba.newnode( 'terrain', attrs={ @@ -943,15 +1029,17 @@ class TipTop(ba.Map): 'lighting': False, 'color': (0.4, 0.4, 0.4), 'background': True, - 'color_texture': self.preloaddata['bgtex'] - }) + 'color_texture': self.preloaddata['bgtex'], + }, + ) self.railing = ba.newnode( 'terrain', attrs={ 'collide_model': self.preloaddata['railing_collide_model'], 'materials': [shared.railing_material], - 'bumper': True - }) + 'bumper': True, + }, + ) gnode = ba.getactivity().globalsnode gnode.tint = (0.8, 0.9, 1.3) gnode.ambient_color = (0.8, 0.9, 1.3) @@ -984,10 +1072,11 @@ class CragCastle(ba.Map): 'tex': ba.gettexture('cragCastleLevelColor'), 'bgtex': ba.gettexture('menuBG'), 'bgmodel': ba.getmodel('thePadBG'), - 'railing_collide_model': - (ba.getcollidemodel('cragCastleLevelBumper')), + 'railing_collide_model': ( + ba.getcollidemodel('cragCastleLevelBumper') + ), 'vr_fill_mound_model': ba.getmodel('cragCastleVRFillMound'), - 'vr_fill_mound_tex': ba.gettexture('vrFillMound') + 'vr_fill_mound_tex': ba.gettexture('vrFillMound'), } # fixme should chop this into vr/non-vr sections return data @@ -1002,38 +1091,45 @@ class CragCastle(ba.Map): 'collide_model': self.preloaddata['collide_model'], 'model': self.preloaddata['model'], 'color_texture': self.preloaddata['tex'], - 'materials': [shared.footing_material] - }) - self.bottom = ba.newnode('terrain', - attrs={ - 'model': self.preloaddata['bottom_model'], - 'lighting': False, - 'color_texture': self.preloaddata['tex'] - }) + 'materials': [shared.footing_material], + }, + ) + self.bottom = ba.newnode( + 'terrain', + attrs={ + 'model': self.preloaddata['bottom_model'], + 'lighting': False, + 'color_texture': self.preloaddata['tex'], + }, + ) self.background = ba.newnode( 'terrain', attrs={ 'model': self.preloaddata['bgmodel'], 'lighting': False, 'background': True, - 'color_texture': self.preloaddata['bgtex'] - }) + 'color_texture': self.preloaddata['bgtex'], + }, + ) self.railing = ba.newnode( 'terrain', attrs={ 'collide_model': self.preloaddata['railing_collide_model'], 'materials': [shared.railing_material], - 'bumper': True - }) - ba.newnode('terrain', - attrs={ - 'model': self.preloaddata['vr_fill_mound_model'], - 'lighting': False, - 'vr_only': True, - 'color': (0.2, 0.25, 0.2), - 'background': True, - 'color_texture': self.preloaddata['vr_fill_mound_tex'] - }) + 'bumper': True, + }, + ) + ba.newnode( + 'terrain', + attrs={ + 'model': self.preloaddata['vr_fill_mound_model'], + 'lighting': False, + 'vr_only': True, + 'color': (0.2, 0.25, 0.2), + 'background': True, + 'color_texture': self.preloaddata['vr_fill_mound_tex'], + }, + ) gnode = ba.getactivity().globalsnode gnode.shadow_ortho = True gnode.shadow_offset = (0, 0, -5.0) @@ -1063,32 +1159,28 @@ class TowerD(ba.Map): @classmethod def on_preload(cls) -> Any: data: dict[str, Any] = { - 'model': - ba.getmodel('towerDLevel'), - 'model_bottom': - ba.getmodel('towerDLevelBottom'), - 'collide_model': - ba.getcollidemodel('towerDLevelCollide'), - 'tex': - ba.gettexture('towerDLevelColor'), - 'bgtex': - ba.gettexture('menuBG'), - 'bgmodel': - ba.getmodel('thePadBG'), - 'player_wall_collide_model': - ba.getcollidemodel('towerDPlayerWall'), - 'player_wall_material': - ba.Material() + 'model': ba.getmodel('towerDLevel'), + 'model_bottom': ba.getmodel('towerDLevelBottom'), + 'collide_model': ba.getcollidemodel('towerDLevelCollide'), + 'tex': ba.gettexture('towerDLevelColor'), + 'bgtex': ba.gettexture('menuBG'), + 'bgmodel': ba.getmodel('thePadBG'), + 'player_wall_collide_model': ba.getcollidemodel('towerDPlayerWall'), + 'player_wall_material': ba.Material(), } # fixme should chop this into vr/non-vr sections data['player_wall_material'].add_actions( - actions=('modify_part_collision', 'friction', 0.0)) + actions=('modify_part_collision', 'friction', 0.0) + ) # anything that needs to hit the wall can apply this material data['collide_with_wall_material'] = ba.Material() data['player_wall_material'].add_actions( - conditions=('they_dont_have_material', - data['collide_with_wall_material']), - actions=('modify_part_collision', 'collide', False)) + conditions=( + 'they_dont_have_material', + data['collide_with_wall_material'], + ), + actions=('modify_part_collision', 'collide', False), + ) data['vr_fill_mound_model'] = ba.getmodel('stepRightUpVRFillMound') data['vr_fill_mound_tex'] = ba.gettexture('vrFillMound') return data @@ -1103,49 +1195,53 @@ class TowerD(ba.Map): 'collide_model': self.preloaddata['collide_model'], 'model': self.preloaddata['model'], 'color_texture': self.preloaddata['tex'], - 'materials': [shared.footing_material] - }) + 'materials': [shared.footing_material], + }, + ) self.node_bottom = ba.newnode( 'terrain', delegate=self, attrs={ 'model': self.preloaddata['model_bottom'], 'lighting': False, - 'color_texture': self.preloaddata['tex'] - }) - ba.newnode('terrain', - attrs={ - 'model': self.preloaddata['vr_fill_mound_model'], - 'lighting': False, - 'vr_only': True, - 'color': (0.53, 0.57, 0.5), - 'background': True, - 'color_texture': self.preloaddata['vr_fill_mound_tex'] - }) + 'color_texture': self.preloaddata['tex'], + }, + ) + ba.newnode( + 'terrain', + attrs={ + 'model': self.preloaddata['vr_fill_mound_model'], + 'lighting': False, + 'vr_only': True, + 'color': (0.53, 0.57, 0.5), + 'background': True, + 'color_texture': self.preloaddata['vr_fill_mound_tex'], + }, + ) self.background = ba.newnode( 'terrain', attrs={ 'model': self.preloaddata['bgmodel'], 'lighting': False, 'background': True, - 'color_texture': self.preloaddata['bgtex'] - }) + 'color_texture': self.preloaddata['bgtex'], + }, + ) self.player_wall = ba.newnode( 'terrain', attrs={ 'collide_model': self.preloaddata['player_wall_collide_model'], 'affect_bg_dynamics': False, - 'materials': [self.preloaddata['player_wall_material']] - }) + 'materials': [self.preloaddata['player_wall_material']], + }, + ) gnode = ba.getactivity().globalsnode gnode.tint = (1.15, 1.11, 1.03) gnode.ambient_color = (1.2, 1.1, 1.0) gnode.vignette_outer = (0.7, 0.73, 0.7) gnode.vignette_inner = (0.95, 0.95, 0.95) - def is_point_near_edge(self, - point: ba.Vec3, - running: bool = False) -> bool: + def is_point_near_edge(self, point: ba.Vec3, running: bool = False) -> bool: # see if we're within edge_box boxes = self.defs.boxes box_position = boxes['edge_box'][0:3] @@ -1157,8 +1253,9 @@ class TowerD(ba.Map): xpos2 = (point.x - box_position2[0]) / box_scale2[0] zpos2 = (point.z - box_position2[2]) / box_scale2[2] # if we're outside of *both* boxes we're near the edge - return ((xpos < -0.5 or xpos > 0.5 or zpos < -0.5 or zpos > 0.5) and - (xpos2 < -0.5 or xpos2 > 0.5 or zpos2 < -0.5 or zpos2 > 0.5)) + return (xpos < -0.5 or xpos > 0.5 or zpos < -0.5 or zpos > 0.5) and ( + xpos2 < -0.5 or xpos2 > 0.5 or zpos2 < -0.5 or zpos2 > 0.5 + ) class HappyThoughts(ba.Map): @@ -1172,7 +1269,11 @@ class HappyThoughts(ba.Map): def get_play_types(cls) -> list[str]: """Return valid play types for this map.""" return [ - 'melee', 'keep_away', 'team_flag', 'conquest', 'king_of_the_hill' + 'melee', + 'keep_away', + 'team_flag', + 'conquest', + 'king_of_the_hill', ] @classmethod @@ -1189,7 +1290,7 @@ class HappyThoughts(ba.Map): 'tex': ba.gettexture('alwaysLandLevelColor'), 'bgtex': ba.gettexture('alwaysLandBGColor'), 'vr_fill_mound_model': ba.getmodel('alwaysLandVRFillMound'), - 'vr_fill_mound_tex': ba.gettexture('vrFillMound') + 'vr_fill_mound_tex': ba.gettexture('vrFillMound'), } return data @@ -1207,31 +1308,37 @@ class HappyThoughts(ba.Map): 'collide_model': self.preloaddata['collide_model'], 'model': self.preloaddata['model'], 'color_texture': self.preloaddata['tex'], - 'materials': [shared.footing_material] - }) - self.bottom = ba.newnode('terrain', - attrs={ - 'model': self.preloaddata['bottom_model'], - 'lighting': False, - 'color_texture': self.preloaddata['tex'] - }) + 'materials': [shared.footing_material], + }, + ) + self.bottom = ba.newnode( + 'terrain', + attrs={ + 'model': self.preloaddata['bottom_model'], + 'lighting': False, + 'color_texture': self.preloaddata['tex'], + }, + ) self.background = ba.newnode( 'terrain', attrs={ 'model': self.preloaddata['bgmodel'], 'lighting': False, 'background': True, - 'color_texture': self.preloaddata['bgtex'] - }) - ba.newnode('terrain', - attrs={ - 'model': self.preloaddata['vr_fill_mound_model'], - 'lighting': False, - 'vr_only': True, - 'color': (0.2, 0.25, 0.2), - 'background': True, - 'color_texture': self.preloaddata['vr_fill_mound_tex'] - }) + 'color_texture': self.preloaddata['bgtex'], + }, + ) + ba.newnode( + 'terrain', + attrs={ + 'model': self.preloaddata['vr_fill_mound_model'], + 'lighting': False, + 'vr_only': True, + 'color': (0.2, 0.25, 0.2), + 'background': True, + 'color_texture': self.preloaddata['vr_fill_mound_tex'], + }, + ) gnode = ba.getactivity().globalsnode gnode.happy_thoughts_mode = True gnode.shadow_offset = (0.0, 8.0, 5.0) @@ -1243,25 +1350,24 @@ class HappyThoughts(ba.Map): self.is_flying = True # throw out some tips on flying - txt = ba.newnode('text', - attrs={ - 'text': ba.Lstr(resource='pressJumpToFlyText'), - 'scale': 1.2, - 'maxwidth': 800, - 'position': (0, 200), - 'shadow': 0.5, - 'flatness': 0.5, - 'h_align': 'center', - 'v_attach': 'bottom' - }) - cmb = ba.newnode('combine', - owner=txt, - attrs={ - 'size': 4, - 'input0': 0.3, - 'input1': 0.9, - 'input2': 0.0 - }) + txt = ba.newnode( + 'text', + attrs={ + 'text': ba.Lstr(resource='pressJumpToFlyText'), + 'scale': 1.2, + 'maxwidth': 800, + 'position': (0, 200), + 'shadow': 0.5, + 'flatness': 0.5, + 'h_align': 'center', + 'v_attach': 'bottom', + }, + ) + cmb = ba.newnode( + 'combine', + owner=txt, + attrs={'size': 4, 'input0': 0.3, 'input1': 0.9, 'input2': 0.0}, + ) ba.animate(cmb, 'input3', {3.0: 0, 4.0: 1, 9.0: 1, 10.0: 0}) cmb.connectattr('output', txt, 'color') ba.timer(10.0, txt.delete) @@ -1293,7 +1399,7 @@ class StepRightUp(ba.Map): 'bgtex': ba.gettexture('menuBG'), 'bgmodel': ba.getmodel('thePadBG'), 'vr_fill_mound_model': ba.getmodel('stepRightUpVRFillMound'), - 'vr_fill_mound_tex': ba.gettexture('vrFillMound') + 'vr_fill_mound_tex': ba.gettexture('vrFillMound'), } # fixme should chop this into vr/non-vr chunks return data @@ -1308,33 +1414,38 @@ class StepRightUp(ba.Map): 'collide_model': self.preloaddata['collide_model'], 'model': self.preloaddata['model'], 'color_texture': self.preloaddata['tex'], - 'materials': [shared.footing_material] - }) + 'materials': [shared.footing_material], + }, + ) self.node_bottom = ba.newnode( 'terrain', delegate=self, attrs={ 'model': self.preloaddata['model_bottom'], 'lighting': False, - 'color_texture': self.preloaddata['tex'] - }) - ba.newnode('terrain', - attrs={ - 'model': self.preloaddata['vr_fill_mound_model'], - 'lighting': False, - 'vr_only': True, - 'color': (0.53, 0.57, 0.5), - 'background': True, - 'color_texture': self.preloaddata['vr_fill_mound_tex'] - }) + 'color_texture': self.preloaddata['tex'], + }, + ) + ba.newnode( + 'terrain', + attrs={ + 'model': self.preloaddata['vr_fill_mound_model'], + 'lighting': False, + 'vr_only': True, + 'color': (0.53, 0.57, 0.5), + 'background': True, + 'color_texture': self.preloaddata['vr_fill_mound_tex'], + }, + ) self.background = ba.newnode( 'terrain', attrs={ 'model': self.preloaddata['bgmodel'], 'lighting': False, 'background': True, - 'color_texture': self.preloaddata['bgtex'] - }) + 'color_texture': self.preloaddata['bgtex'], + }, + ) gnode = ba.getactivity().globalsnode gnode.tint = (1.2, 1.1, 1.0) gnode.ambient_color = (1.2, 1.1, 1.0) @@ -1367,19 +1478,24 @@ class Courtyard(ba.Map): 'tex': ba.gettexture('courtyardLevelColor'), 'bgtex': ba.gettexture('menuBG'), 'bgmodel': ba.getmodel('thePadBG'), - 'player_wall_collide_model': - (ba.getcollidemodel('courtyardPlayerWall')), - 'player_wall_material': ba.Material() + 'player_wall_collide_model': ( + ba.getcollidemodel('courtyardPlayerWall') + ), + 'player_wall_material': ba.Material(), } # FIXME: Chop this into vr and non-vr chunks. data['player_wall_material'].add_actions( - actions=('modify_part_collision', 'friction', 0.0)) + actions=('modify_part_collision', 'friction', 0.0) + ) # anything that needs to hit the wall should apply this. data['collide_with_wall_material'] = ba.Material() data['player_wall_material'].add_actions( - conditions=('they_dont_have_material', - data['collide_with_wall_material']), - actions=('modify_part_collision', 'collide', False)) + conditions=( + 'they_dont_have_material', + data['collide_with_wall_material'], + ), + actions=('modify_part_collision', 'collide', False), + ) data['vr_fill_mound_model'] = ba.getmodel('stepRightUpVRFillMound') data['vr_fill_mound_tex'] = ba.gettexture('vrFillMound') return data @@ -1394,31 +1510,37 @@ class Courtyard(ba.Map): 'collide_model': self.preloaddata['collide_model'], 'model': self.preloaddata['model'], 'color_texture': self.preloaddata['tex'], - 'materials': [shared.footing_material] - }) + 'materials': [shared.footing_material], + }, + ) self.background = ba.newnode( 'terrain', attrs={ 'model': self.preloaddata['bgmodel'], 'lighting': False, 'background': True, - 'color_texture': self.preloaddata['bgtex'] - }) - self.bottom = ba.newnode('terrain', - attrs={ - 'model': self.preloaddata['model_bottom'], - 'lighting': False, - 'color_texture': self.preloaddata['tex'] - }) - ba.newnode('terrain', - attrs={ - 'model': self.preloaddata['vr_fill_mound_model'], - 'lighting': False, - 'vr_only': True, - 'color': (0.53, 0.57, 0.5), - 'background': True, - 'color_texture': self.preloaddata['vr_fill_mound_tex'] - }) + 'color_texture': self.preloaddata['bgtex'], + }, + ) + self.bottom = ba.newnode( + 'terrain', + attrs={ + 'model': self.preloaddata['model_bottom'], + 'lighting': False, + 'color_texture': self.preloaddata['tex'], + }, + ) + ba.newnode( + 'terrain', + attrs={ + 'model': self.preloaddata['vr_fill_mound_model'], + 'lighting': False, + 'vr_only': True, + 'color': (0.53, 0.57, 0.5), + 'background': True, + 'color_texture': self.preloaddata['vr_fill_mound_tex'], + }, + ) # in co-op mode games, put up a wall to prevent players # from getting in the turrets (that would foil our brilliant AI) if isinstance(ba.getsession(), ba.CoopSession): @@ -1428,17 +1550,16 @@ class Courtyard(ba.Map): attrs={ 'collide_model': cmodel, 'affect_bg_dynamics': False, - 'materials': [self.preloaddata['player_wall_material']] - }) + 'materials': [self.preloaddata['player_wall_material']], + }, + ) gnode = ba.getactivity().globalsnode gnode.tint = (1.2, 1.17, 1.1) gnode.ambient_color = (1.2, 1.17, 1.1) gnode.vignette_outer = (0.6, 0.6, 0.64) gnode.vignette_inner = (0.95, 0.95, 0.93) - def is_point_near_edge(self, - point: ba.Vec3, - running: bool = False) -> bool: + def is_point_near_edge(self, point: ba.Vec3, running: bool = False) -> bool: # count anything off our ground level as safe (for our platforms) # see if we're within edge_box box_position = self.defs.boxes['edge_box'][0:3] @@ -1476,7 +1597,7 @@ class Rampage(ba.Map): 'bgmodel': ba.getmodel('rampageBG'), 'bgmodel2': ba.getmodel('rampageBG2'), 'vr_fill_model': ba.getmodel('rampageVRFill'), - 'railing_collide_model': ba.getcollidemodel('rampageBumper') + 'railing_collide_model': ba.getcollidemodel('rampageBumper'), } return data @@ -1490,53 +1611,60 @@ class Rampage(ba.Map): 'collide_model': self.preloaddata['collide_model'], 'model': self.preloaddata['model'], 'color_texture': self.preloaddata['tex'], - 'materials': [shared.footing_material] - }) + 'materials': [shared.footing_material], + }, + ) self.background = ba.newnode( 'terrain', attrs={ 'model': self.preloaddata['bgmodel'], 'lighting': False, 'background': True, - 'color_texture': self.preloaddata['bgtex'] - }) - self.bottom = ba.newnode('terrain', - attrs={ - 'model': self.preloaddata['bottom_model'], - 'lighting': False, - 'color_texture': self.preloaddata['tex'] - }) - self.bg2 = ba.newnode('terrain', - attrs={ - 'model': self.preloaddata['bgmodel2'], - 'lighting': False, - 'background': True, - 'color_texture': self.preloaddata['bgtex2'] - }) - ba.newnode('terrain', - attrs={ - 'model': self.preloaddata['vr_fill_model'], - 'lighting': False, - 'vr_only': True, - 'background': True, - 'color_texture': self.preloaddata['bgtex2'] - }) + 'color_texture': self.preloaddata['bgtex'], + }, + ) + self.bottom = ba.newnode( + 'terrain', + attrs={ + 'model': self.preloaddata['bottom_model'], + 'lighting': False, + 'color_texture': self.preloaddata['tex'], + }, + ) + self.bg2 = ba.newnode( + 'terrain', + attrs={ + 'model': self.preloaddata['bgmodel2'], + 'lighting': False, + 'background': True, + 'color_texture': self.preloaddata['bgtex2'], + }, + ) + ba.newnode( + 'terrain', + attrs={ + 'model': self.preloaddata['vr_fill_model'], + 'lighting': False, + 'vr_only': True, + 'background': True, + 'color_texture': self.preloaddata['bgtex2'], + }, + ) self.railing = ba.newnode( 'terrain', attrs={ 'collide_model': self.preloaddata['railing_collide_model'], 'materials': [shared.railing_material], - 'bumper': True - }) + 'bumper': True, + }, + ) gnode = ba.getactivity().globalsnode gnode.tint = (1.2, 1.1, 0.97) gnode.ambient_color = (1.3, 1.2, 1.03) gnode.vignette_outer = (0.62, 0.64, 0.69) gnode.vignette_inner = (0.97, 0.95, 0.93) - def is_point_near_edge(self, - point: ba.Vec3, - running: bool = False) -> bool: + def is_point_near_edge(self, point: ba.Vec3, running: bool = False) -> bool: box_position = self.defs.boxes['edge_box'][0:3] box_scale = self.defs.boxes['edge_box'][6:9] xpos = (point.x - box_position[0]) / box_scale[0] diff --git a/dist/ba_data/python/bastd/stdmap.py b/dist/ba_data/python/bastd/stdmap.py index 82101d1..505705a 100644 --- a/dist/ba_data/python/bastd/stdmap.py +++ b/dist/ba_data/python/bastd/stdmap.py @@ -14,18 +14,19 @@ if TYPE_CHECKING: def _get_map_data(name: str) -> dict[str, Any]: import json + print('Would get map data', name) - with open('ba_data/data/maps/' + name + '.json', - encoding='utf-8') as infile: + with open( + 'ba_data/data/maps/' + name + '.json', encoding='utf-8' + ) as infile: mapdata = json.loads(infile.read()) assert isinstance(mapdata, dict) return mapdata class StdMap(ba.Map): - """A map completely defined by asset data. + """A map completely defined by asset data.""" - """ _data: dict[str, Any] | None = None @classmethod diff --git a/dist/ba_data/python/bastd/tutorial.py b/dist/ba_data/python/bastd/tutorial.py index c92aed8..d2cd7b0 100644 --- a/dist/ba_data/python/bastd/tutorial.py +++ b/dist/ba_data/python/bastd/tutorial.py @@ -32,12 +32,13 @@ def _safesetattr(node: ba.Node | None, attr: str, value: Any) -> None: class ButtonPress: - - def __init__(self, - button: str, - delay: int = 0, - release: bool = True, - release_delay: int = 0): + def __init__( + self, + button: str, + delay: int = 0, + release: bool = True, + release_delay: int = 0, + ): self._button = button self._delay = delay self._release = release @@ -94,29 +95,37 @@ class ButtonPress: else: ba.timer(self._delay, call, timeformat=ba.TimeFormat.MILLISECONDS) if img is not None: - ba.timer(self._delay, - ba.Call(_safesetattr, img, 'color', c_bright), - timeformat=ba.TimeFormat.MILLISECONDS) - ba.timer(self._delay, - ba.Call(_safesetattr, img, 'vr_depth', -30), - timeformat=ba.TimeFormat.MILLISECONDS) + ba.timer( + self._delay, + ba.Call(_safesetattr, img, 'color', c_bright), + timeformat=ba.TimeFormat.MILLISECONDS, + ) + ba.timer( + self._delay, + ba.Call(_safesetattr, img, 'vr_depth', -30), + timeformat=ba.TimeFormat.MILLISECONDS, + ) if self._release: if self._delay == 0 and self._release_delay == 0: release_call() else: - ba.timer(0.001 * (self._delay + self._release_delay), - release_call) + ba.timer( + 0.001 * (self._delay + self._release_delay), release_call + ) if img is not None: - ba.timer(self._delay + self._release_delay + 100, - ba.Call(_safesetattr, img, 'color', color), - timeformat=ba.TimeFormat.MILLISECONDS) - ba.timer(self._delay + self._release_delay + 100, - ba.Call(_safesetattr, img, 'vr_depth', -20), - timeformat=ba.TimeFormat.MILLISECONDS) + ba.timer( + self._delay + self._release_delay + 100, + ba.Call(_safesetattr, img, 'color', color), + timeformat=ba.TimeFormat.MILLISECONDS, + ) + ba.timer( + self._delay + self._release_delay + 100, + ba.Call(_safesetattr, img, 'vr_depth', -20), + timeformat=ba.TimeFormat.MILLISECONDS, + ) class ButtonRelease: - def __init__(self, button: str, delay: int = 0): self._button = button self._delay = delay @@ -154,12 +163,16 @@ class ButtonRelease: else: ba.timer(self._delay, call, timeformat=ba.TimeFormat.MILLISECONDS) if img is not None: - ba.timer(self._delay + 100, - ba.Call(_safesetattr, img, 'color', color), - timeformat=ba.TimeFormat.MILLISECONDS) - ba.timer(self._delay + 100, - ba.Call(_safesetattr, img, 'vr_depth', -20), - timeformat=ba.TimeFormat.MILLISECONDS) + ba.timer( + self._delay + 100, + ba.Call(_safesetattr, img, 'color', color), + timeformat=ba.TimeFormat.MILLISECONDS, + ) + ba.timer( + self._delay + 100, + ba.Call(_safesetattr, img, 'vr_depth', -20), + timeformat=ba.TimeFormat.MILLISECONDS, + ) class Player(ba.Player['Team']): @@ -177,9 +190,9 @@ class Team(ba.Team[Player]): class TutorialActivity(ba.Activity[Player, Team]): - def __init__(self, settings: dict | None = None): from bastd.maps import Rampage + if settings is None: settings = {} super().__init__(settings) @@ -247,46 +260,53 @@ class TutorialActivity(ba.Activity[Player, Team]): buttons_y = 160 # Need different versions of this: taps/buttons/keys. - self.text = ba.newnode('text', - attrs={ - 'text': '', - 'scale': 1.9, - 'position': (0, text_y), - 'maxwidth': 500, - 'flatness': 0.0, - 'shadow': 0.5, - 'h_align': 'center', - 'v_align': 'center', - 'v_attach': 'center' - }) + self.text = ba.newnode( + 'text', + attrs={ + 'text': '', + 'scale': 1.9, + 'position': (0, text_y), + 'maxwidth': 500, + 'flatness': 0.0, + 'shadow': 0.5, + 'h_align': 'center', + 'v_align': 'center', + 'v_attach': 'center', + }, + ) # Need different versions of this: taps/buttons/keys. - txt = ba.Lstr( - resource=self._r + - '.cpuBenchmarkText') if self._benchmark_type == 'cpu' else ba.Lstr( - resource=self._r + '.toSkipPressAnythingText') - t = self._skip_text = ba.newnode('text', - attrs={ - 'text': txt, - 'maxwidth': 900, - 'scale': 1.1, - 'vr_depth': 100, - 'position': (0, 30), - 'h_align': 'center', - 'v_align': 'center', - 'v_attach': 'bottom' - }) + txt = ( + ba.Lstr(resource=self._r + '.cpuBenchmarkText') + if self._benchmark_type == 'cpu' + else ba.Lstr(resource=self._r + '.toSkipPressAnythingText') + ) + t = self._skip_text = ba.newnode( + 'text', + attrs={ + 'text': txt, + 'maxwidth': 900, + 'scale': 1.1, + 'vr_depth': 100, + 'position': (0, 30), + 'h_align': 'center', + 'v_align': 'center', + 'v_attach': 'bottom', + }, + ) ba.animate(t, 'opacity', {1.0: 0.0, 2.0: 0.7}) - self._skip_count_text = ba.newnode('text', - attrs={ - 'text': '', - 'scale': 1.4, - 'vr_depth': 90, - 'position': (0, 70), - 'h_align': 'center', - 'v_align': 'center', - 'v_attach': 'bottom' - }) + self._skip_count_text = ba.newnode( + 'text', + attrs={ + 'text': '', + 'scale': 1.4, + 'vr_depth': 90, + 'position': (0, 70), + 'h_align': 'center', + 'v_align': 'center', + 'v_attach': 'bottom', + }, + ) ouya = False @@ -303,18 +323,21 @@ class TutorialActivity(ba.Activity[Player, Team]): return 0.6 * r, 0.6 * g, 0.6 * b self.jump_image_color = c = _sc(0.4, 1, 0.4) - self.jump_image = ba.newnode('image', - attrs={ - 'texture': self._jump_button_tex, - 'absolute_scale': True, - 'vr_depth': -20, - 'position': p, - 'scale': (image_size, image_size), - 'color': c - }) + self.jump_image = ba.newnode( + 'image', + attrs={ + 'texture': self._jump_button_tex, + 'absolute_scale': True, + 'vr_depth': -20, + 'position': p, + 'scale': (image_size, image_size), + 'color': c, + }, + ) p = (position[0] + center_offs - offs, position[1]) - self.punch_image_color = c = _sc(0.2, 0.6, 1) if ouya else _sc( - 1, 0.7, 0.3) + self.punch_image_color = c = ( + _sc(0.2, 0.6, 1) if ouya else _sc(1, 0.7, 0.3) + ) self.punch_image = ba.newnode( 'image', attrs={ @@ -323,8 +346,9 @@ class TutorialActivity(ba.Activity[Player, Team]): 'vr_depth': -20, 'position': p, 'scale': (image_size, image_size), - 'color': c - }) + 'color': c, + }, + ) p = (position[0] + center_offs + offs, position[1]) self.bomb_image_color = c = _sc(1, 0.3, 0.3) self.bomb_image = ba.newnode( @@ -335,11 +359,13 @@ class TutorialActivity(ba.Activity[Player, Team]): 'vr_depth': -20, 'position': p, 'scale': (image_size, image_size), - 'color': c - }) + 'color': c, + }, + ) p = (position[0] + center_offs, position[1] + offs) - self.pickup_image_color = c = _sc(1, 0.8, 0.3) if ouya else _sc( - 0.5, 0.5, 1) + self.pickup_image_color = c = ( + _sc(1, 0.8, 0.3) if ouya else _sc(0.5, 0.5, 1) + ) self.pickup_image = ba.newnode( 'image', attrs={ @@ -348,11 +374,11 @@ class TutorialActivity(ba.Activity[Player, Team]): 'vr_depth': -20, 'position': p, 'scale': (image_size, image_size), - 'color': c - }) + 'color': c, + }, + ) - self._stick_base_position = p = (position[0] - center_offs, - position[1]) + self._stick_base_position = p = (position[0] - center_offs, position[1]) self._stick_base_image_color = c2 = (0.25, 0.25, 0.25, 1.0) self._stick_base_image = ba.newnode( 'image', @@ -362,21 +388,28 @@ class TutorialActivity(ba.Activity[Player, Team]): 'vr_depth': -40, 'position': p, 'scale': (image_size_2, image_size_2), - 'color': c2 - }) + 'color': c2, + }, + ) self._stick_nub_position = p = (position[0] - center_offs, position[1]) self._stick_nub_image_color = c3 = (0.4, 0.4, 0.4, 1.0) - self._stick_nub_image = ba.newnode('image', - attrs={ - 'texture': ba.gettexture('nub'), - 'absolute_scale': True, - 'position': p, - 'scale': (nub_size, nub_size), - 'color': c3 - }) + self._stick_nub_image = ba.newnode( + 'image', + attrs={ + 'texture': ba.gettexture('nub'), + 'absolute_scale': True, + 'position': p, + 'scale': (nub_size, nub_size), + 'color': c3, + }, + ) self.control_ui_nodes = [ - self.jump_image, self.punch_image, self.bomb_image, - self.pickup_image, self._stick_base_image, self._stick_nub_image + self.jump_image, + self.punch_image, + self.bomb_image, + self.pickup_image, + self._stick_base_image, + self._stick_nub_image, ] for n in self.control_ui_nodes: n.opacity = 0.0 @@ -398,7 +431,7 @@ class TutorialActivity(ba.Activity[Player, Team]): assert self._scale is not None p = [ self._stick_nub_position[0] + x * offs * self._scale, - self._stick_nub_position[1] + y * offs * self._scale + self._stick_nub_position[1] + y * offs * self._scale, ] c = list(self._stick_nub_image_color) if abs(x) > 0.1 or abs(y) > 0.1: @@ -420,7 +453,6 @@ class TutorialActivity(ba.Activity[Player, Team]): try: class Reset: - def __init__(self) -> None: pass @@ -429,17 +461,23 @@ class TutorialActivity(ba.Activity[Player, Team]): # if we're looping, print out how long each cycle took # print out how long each cycle took.. if a.last_start_time is not None: - tval = ba.time( - ba.TimeType.REAL, - ba.TimeFormat.MILLISECONDS) - a.last_start_time + tval = ( + ba.time( + ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS + ) + - a.last_start_time + ) assert isinstance(tval, int) diff = tval a.cycle_times.append(diff) ba.screenmessage( - 'cycle time: ' + str(diff) + ' (average: ' + - str(sum(a.cycle_times) / len(a.cycle_times)) + ')') - tval = ba.time(ba.TimeType.REAL, - ba.TimeFormat.MILLISECONDS) + 'cycle time: ' + + str(diff) + + ' (average: ' + + str(sum(a.cycle_times) / len(a.cycle_times)) + + ')' + ) + tval = ba.time(ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS) assert isinstance(tval, int) a.last_start_time = tval @@ -455,7 +493,6 @@ class TutorialActivity(ba.Activity[Player, Team]): # Can be used for debugging. class SetSpeed: - def __init__(self, speed: int): self._speed = speed @@ -464,7 +501,6 @@ class TutorialActivity(ba.Activity[Player, Team]): ba.internal.set_debug_speed_exponent(self._speed) class RemoveGloves: - def __init__(self) -> None: pass @@ -475,7 +511,6 @@ class TutorialActivity(ba.Activity[Player, Team]): a.current_spaz._gloves_wear_off() class KillSpaz: - def __init__(self, num: int, explode: bool = False): self._num = num self._explode = explode @@ -486,16 +521,17 @@ class TutorialActivity(ba.Activity[Player, Team]): del a.spazzes[self._num] class SpawnSpaz: - - def __init__(self, - num: int, - position: Sequence[float], - color: Sequence[float] = (1.0, 1.0, 1.0), - make_current: bool = False, - relative_to: int | None = None, - name: str | ba.Lstr = '', - flash: bool = True, - angle: float = 0.0): + def __init__( + self, + num: int, + position: Sequence[float], + color: Sequence[float] = (1.0, 1.0, 1.0), + make_current: bool = False, + relative_to: int | None = None, + name: str | ba.Lstr = '', + flash: bool = True, + angle: float = 0.0, + ): self._num = num self._position = position self._make_current = make_current @@ -514,21 +550,25 @@ class TutorialActivity(ba.Activity[Player, Team]): snode = a.spazzes[self._relative_to].node assert snode their_pos = snode.position - pos = (their_pos[0] + self._position[0], - their_pos[1] + self._position[1], - their_pos[2] + self._position[2]) + pos = ( + their_pos[0] + self._position[0], + their_pos[1] + self._position[1], + their_pos[2] + self._position[2], + ) else: pos = self._position # if there's already a spaz at this spot, insta-kill it if self._num in a.spazzes: a.spazzes[self._num].handlemessage( - ba.DieMessage(immediate=True)) + ba.DieMessage(immediate=True) + ) s = a.spazzes[self._num] = basespaz.Spaz( color=self._color, start_invincible=self._flash, - demo_mode=True) + demo_mode=True, + ) # FIXME: Should extend spaz to support Lstr names. assert s.node @@ -544,13 +584,14 @@ class TutorialActivity(ba.Activity[Player, Team]): ba.playsound(a.spawn_sound, position=pos) class Powerup: - - def __init__(self, - num: int, - position: Sequence[float], - color: Sequence[float] = (1.0, 1.0, 1.0), - make_current: bool = False, - relative_to: int | None = None): + def __init__( + self, + num: int, + position: Sequence[float], + color: Sequence[float] = (1.0, 1.0, 1.0), + make_current: bool = False, + relative_to: int | None = None, + ): self._position = position self._relative_to = relative_to @@ -562,17 +603,20 @@ class TutorialActivity(ba.Activity[Player, Team]): snode = a.spazzes[self._relative_to].node assert snode their_pos = snode.position - pos = (their_pos[0] + self._position[0], - their_pos[1] + self._position[1], - their_pos[2] + self._position[2]) + pos = ( + their_pos[0] + self._position[0], + their_pos[1] + self._position[1], + their_pos[2] + self._position[2], + ) else: pos = self._position from bastd.actor import powerupbox - powerupbox.PowerupBox(position=pos, - poweruptype='punch').autoretain() + + powerupbox.PowerupBox( + position=pos, poweruptype='punch' + ).autoretain() class Delay: - def __init__(self, time: int) -> None: self._time = time @@ -580,7 +624,6 @@ class TutorialActivity(ba.Activity[Player, Team]): return self._time class AnalyticsScreen: - def __init__(self, screen: str) -> None: self._screen = screen @@ -588,7 +631,6 @@ class TutorialActivity(ba.Activity[Player, Team]): ba.set_analytics_screen(self._screen) class DelayOld: - def __init__(self, time: int) -> None: self._time = time @@ -596,7 +638,6 @@ class TutorialActivity(ba.Activity[Player, Team]): return int(0.9 * self._time) class DelayOld2: - def __init__(self, time: int) -> None: self._time = time @@ -604,7 +645,6 @@ class TutorialActivity(ba.Activity[Player, Team]): return int(0.8 * self._time) class End: - def __init__(self) -> None: pass @@ -613,7 +653,6 @@ class TutorialActivity(ba.Activity[Player, Team]): a.end() class Move: - def __init__(self, x: float, y: float): self._x = float(x) self._y = float(y) @@ -629,7 +668,6 @@ class TutorialActivity(ba.Activity[Player, Team]): a.set_stick_image_position(self._x, self._y) class MoveLR: - def __init__(self, x: float): self._x = float(x) @@ -639,11 +677,11 @@ class TutorialActivity(ba.Activity[Player, Team]): # FIXME: Game should take floats for this. x_clamped = self._x s.on_move_left_right(x_clamped) - a.set_stick_image_position(self._x, - a.stick_image_position_y) + a.set_stick_image_position( + self._x, a.stick_image_position_y + ) class MoveUD: - def __init__(self, y: float): self._y = float(y) @@ -653,96 +691,106 @@ class TutorialActivity(ba.Activity[Player, Team]): # FIXME: Game should take floats for this. y_clamped = self._y s.on_move_up_down(y_clamped) - a.set_stick_image_position(a.stick_image_position_x, - self._y) + a.set_stick_image_position( + a.stick_image_position_x, self._y + ) class Bomb(ButtonPress): - - def __init__(self, - delay: int = 0, - release: bool = True, - release_delay: int = 500): - ButtonPress.__init__(self, - 'bomb', - delay=delay, - release=release, - release_delay=release_delay) + def __init__( + self, + delay: int = 0, + release: bool = True, + release_delay: int = 500, + ): + ButtonPress.__init__( + self, + 'bomb', + delay=delay, + release=release, + release_delay=release_delay, + ) class Jump(ButtonPress): - - def __init__(self, - delay: int = 0, - release: bool = True, - release_delay: int = 500): - ButtonPress.__init__(self, - 'jump', - delay=delay, - release=release, - release_delay=release_delay) + def __init__( + self, + delay: int = 0, + release: bool = True, + release_delay: int = 500, + ): + ButtonPress.__init__( + self, + 'jump', + delay=delay, + release=release, + release_delay=release_delay, + ) class Punch(ButtonPress): - - def __init__(self, - delay: int = 0, - release: bool = True, - release_delay: int = 500): - ButtonPress.__init__(self, - 'punch', - delay=delay, - release=release, - release_delay=release_delay) + def __init__( + self, + delay: int = 0, + release: bool = True, + release_delay: int = 500, + ): + ButtonPress.__init__( + self, + 'punch', + delay=delay, + release=release, + release_delay=release_delay, + ) class PickUp(ButtonPress): - - def __init__(self, - delay: int = 0, - release: bool = True, - release_delay: int = 500): - ButtonPress.__init__(self, - 'pickUp', - delay=delay, - release=release, - release_delay=release_delay) + def __init__( + self, + delay: int = 0, + release: bool = True, + release_delay: int = 500, + ): + ButtonPress.__init__( + self, + 'pickUp', + delay=delay, + release=release, + release_delay=release_delay, + ) class Run(ButtonPress): - - def __init__(self, - delay: int = 0, - release: bool = True, - release_delay: int = 500): - ButtonPress.__init__(self, - 'run', - delay=delay, - release=release, - release_delay=release_delay) + def __init__( + self, + delay: int = 0, + release: bool = True, + release_delay: int = 500, + ): + ButtonPress.__init__( + self, + 'run', + delay=delay, + release=release, + release_delay=release_delay, + ) class BombRelease(ButtonRelease): - def __init__(self, delay: int = 0): super().__init__('bomb', delay=delay) class JumpRelease(ButtonRelease): - def __init__(self, delay: int = 0): super().__init__('jump', delay=delay) class PunchRelease(ButtonRelease): - def __init__(self, delay: int = 0): super().__init__('punch', delay=delay) class PickUpRelease(ButtonRelease): - def __init__(self, delay: int = 0): super().__init__('pickUp', delay=delay) class RunRelease(ButtonRelease): - def __init__(self, delay: int = 0): super().__init__('run', delay=delay) class ShowControls: - def __init__(self) -> None: pass @@ -751,7 +799,6 @@ class TutorialActivity(ba.Activity[Player, Team]): ba.animate(n, 'opacity', {0.0: 0.0, 1.0: 1.0}) class Text: - def __init__(self, text: str | ba.Lstr): self.text = text @@ -760,7 +807,6 @@ class TutorialActivity(ba.Activity[Player, Team]): a.text.text = self.text class PrintPos: - def __init__(self, spaz_num: int | None = None): self._spaz_num = spaz_num @@ -774,7 +820,6 @@ class TutorialActivity(ba.Activity[Player, Team]): print('RestorePos(' + str((t[0], t[1] - 1.0, t[2])) + '),') class RestorePos: - def __init__(self, pos: Sequence[float]) -> None: self._pos = pos @@ -784,11 +829,12 @@ class TutorialActivity(ba.Activity[Player, Team]): s.handlemessage(ba.StandMessage(self._pos, 0)) class Celebrate: - - def __init__(self, - celebrate_type: str = 'both', - spaz_num: int | None = None, - duration: int = 1000): + def __init__( + self, + celebrate_type: str = 'both', + spaz_num: int | None = None, + duration: int = 1000, + ): self._spaz_num = spaz_num self._celebrate_type = celebrate_type self._duration = duration @@ -806,8 +852,9 @@ class TutorialActivity(ba.Activity[Player, Team]): elif self._celebrate_type == 'both': s.node.handlemessage('celebrate', self._duration) else: - raise Exception('invalid celebrate type ' + - self._celebrate_type) + raise Exception( + 'invalid celebrate type ' + self._celebrate_type + ) self._entries = [ Reset(), @@ -818,10 +865,11 @@ class TutorialActivity(ba.Activity[Player, Team]): Celebrate('left'), DelayOld(2000), Text( - ba.Lstr(resource=self._r + '.phrase02Text', - subs=[ - ('${APP_NAME}', ba.Lstr(resource='titleText')) - ])), # welcome to + ba.Lstr( + resource=self._r + '.phrase02Text', + subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))], + ) + ), # welcome to DelayOld(80), Run(release=False), Jump(release=False), @@ -845,8 +893,9 @@ class TutorialActivity(ba.Activity[Player, Team]): DelayOld(80), MoveUD(0), DelayOld(1500), - Text(ba.Lstr(resource=self._r + - '.phrase03Text')), # here's a few tips + Text( + ba.Lstr(resource=self._r + '.phrase03Text') + ), # here's a few tips DelayOld(1000), ShowControls(), DelayOld(1000), @@ -856,10 +905,11 @@ class TutorialActivity(ba.Activity[Player, Team]): DelayOld(1000), AnalyticsScreen('Tutorial Section 2'), Text( - ba.Lstr(resource=self._r + '.phrase04Text', - subs=[ - ('${APP_NAME}', ba.Lstr(resource='titleText')) - ])), # many things are based on physics + ba.Lstr( + resource=self._r + '.phrase04Text', + subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))], + ) + ), # many things are based on physics DelayOld(20), MoveUD(0), DelayOld(60), @@ -1214,28 +1264,36 @@ class TutorialActivity(ba.Activity[Player, Team]): DelayOld(100), Move(0, 0), DelayOld(1000), - Text(ba.Lstr(resource=self._r + - '.phrase05Text')), # for example when you punch.. + Text( + ba.Lstr(resource=self._r + '.phrase05Text') + ), # for example when you punch.. DelayOld(510), Move(0, -0.01), DelayOld(100), Move(0, 0), DelayOld(500), - SpawnSpaz(0, (-0.09249162673950195, 4.337906360626221, -2.3), - make_current=True, - flash=False), - SpawnSpaz(1, (-3.1, 4.3, -2.0), - make_current=False, - color=(1, 1, 0.4), - name=ba.Lstr(resource=self._r + '.randomName1Text')), + SpawnSpaz( + 0, + (-0.09249162673950195, 4.337906360626221, -2.3), + make_current=True, + flash=False, + ), + SpawnSpaz( + 1, + (-3.1, 4.3, -2.0), + make_current=False, + color=(1, 1, 0.4), + name=ba.Lstr(resource=self._r + '.randomName1Text'), + ), Move(-1.0, 0), DelayOld(1050), Move(0, -0.01), DelayOld(100), Move(0, 0), DelayOld(1000), - Text(ba.Lstr(resource=self._r + - '.phrase06Text')), # your damage is based + Text( + ba.Lstr(resource=self._r + '.phrase06Text') + ), # your damage is based DelayOld(1200), Move(-0.05, 0), DelayOld(200), @@ -1249,17 +1307,22 @@ class TutorialActivity(ba.Activity[Player, Team]): DelayOld(100), Move(0, 0), Text( - ba.Lstr(resource=self._r + '.phrase07Text', - subs=[('${NAME}', - ba.Lstr(resource=self._r + - '.randomName1Text')) - ])), # see that didn't hurt fred + ba.Lstr( + resource=self._r + '.phrase07Text', + subs=[ + ( + '${NAME}', + ba.Lstr(resource=self._r + '.randomName1Text'), + ) + ], + ) + ), # see that didn't hurt fred DelayOld(2000), Celebrate('right', spaz_num=1), DelayOld(1400), - Text(ba.Lstr( - resource=self._r + - '.phrase08Text')), # lets jump and spin to get more speed + Text( + ba.Lstr(resource=self._r + '.phrase08Text') + ), # lets jump and spin to get more speed DelayOld(30), MoveLR(0), DelayOld(40), @@ -1458,22 +1521,27 @@ class TutorialActivity(ba.Activity[Player, Team]): MoveLR(0.00390637), Move(0, 0), DelayOld(1000), - Text(ba.Lstr(resource=self._r + - '.phrase09Text')), # ah that's better + Text( + ba.Lstr(resource=self._r + '.phrase09Text') + ), # ah that's better DelayOld(1900), AnalyticsScreen('Tutorial Section 3'), - Text(ba.Lstr(resource=self._r + - '.phrase10Text')), # running also helps + Text( + ba.Lstr(resource=self._r + '.phrase10Text') + ), # running also helps DelayOld(100), - SpawnSpaz(0, (-3.2, 4.3, -4.4), make_current=True, - flash=False), - SpawnSpaz(1, (3.3, 4.2, -5.8), - make_current=False, - color=(0.9, 0.5, 1.0), - name=ba.Lstr(resource=self._r + '.randomName2Text')), + SpawnSpaz(0, (-3.2, 4.3, -4.4), make_current=True, flash=False), + SpawnSpaz( + 1, + (3.3, 4.2, -5.8), + make_current=False, + color=(0.9, 0.5, 1.0), + name=ba.Lstr(resource=self._r + '.randomName2Text'), + ), DelayOld(1800), - Text(ba.Lstr(resource=self._r + - '.phrase11Text')), # hold ANY button to run + Text( + ba.Lstr(resource=self._r + '.phrase11Text') + ), # hold ANY button to run DelayOld(300), MoveUD(0), DelayOld(20), @@ -1729,21 +1797,23 @@ class TutorialActivity(ba.Activity[Player, Team]): MoveUD(0), AnalyticsScreen('Tutorial Section 4'), Text( - ba.Lstr(resource=self._r + - '.phrase12Text')), # for extra-awesome punches,... + ba.Lstr(resource=self._r + '.phrase12Text') + ), # for extra-awesome punches,... DelayOld(200), SpawnSpaz( 0, (2.368781805038452, 4.337533950805664, -4.360159873962402), make_current=True, - flash=False), + flash=False, + ), SpawnSpaz( 1, (-3.2, 4.3, -4.5), make_current=False, color=(1.0, 0.7, 0.3), # name=R.randomName3Text), - name=ba.Lstr(resource=self._r + '.randomName3Text')), + name=ba.Lstr(resource=self._r + '.randomName3Text'), + ), DelayOld(100), Powerup(1, (2.5, 0.0, 0), relative_to=0), Move(1, 0), @@ -1940,29 +2010,45 @@ class TutorialActivity(ba.Activity[Player, Team]): DelayOld(750), MoveLR(0), Text( - ba.Lstr(resource=self._r + '.phrase13Text', - subs=[('${NAME}', - ba.Lstr(resource=self._r + - '.randomName3Text')) - ])), # whoops sorry bill + ba.Lstr( + resource=self._r + '.phrase13Text', + subs=[ + ( + '${NAME}', + ba.Lstr(resource=self._r + '.randomName3Text'), + ) + ], + ) + ), # whoops sorry bill RemoveGloves(), DelayOld(2000), AnalyticsScreen('Tutorial Section 5'), Text( - ba.Lstr(resource=self._r + '.phrase14Text', - subs=[('${NAME}', - ba.Lstr(resource=self._r + - '.randomName4Text'))]) + ba.Lstr( + resource=self._r + '.phrase14Text', + subs=[ + ( + '${NAME}', + ba.Lstr(resource=self._r + '.randomName4Text'), + ) + ], + ) ), # you can pick up and throw things such as chuck here - SpawnSpaz(0, (-4.0, 4.3, -2.5), - make_current=True, - flash=False, - angle=90), - SpawnSpaz(1, (5, 0, -1.0), - relative_to=0, - make_current=False, - color=(0.4, 1.0, 0.7), - name=ba.Lstr(resource=self._r + '.randomName4Text')), + SpawnSpaz( + 0, + (-4.0, 4.3, -2.5), + make_current=True, + flash=False, + angle=90, + ), + SpawnSpaz( + 1, + (5, 0, -1.0), + relative_to=0, + make_current=False, + color=(0.4, 1.0, 0.7), + name=ba.Lstr(resource=self._r + '.randomName4Text'), + ), DelayOld(1000), Celebrate('left', 1, duration=1000), Move(1, 0.2), @@ -1980,17 +2066,17 @@ class TutorialActivity(ba.Activity[Player, Team]): DelayOld(800), Move(0, 0), DelayOld(800), - SpawnSpaz(0, (1.5, 4.3, -4.0), - make_current=True, - flash=False, - angle=0), + SpawnSpaz( + 0, (1.5, 4.3, -4.0), make_current=True, flash=False, angle=0 + ), AnalyticsScreen('Tutorial Section 6'), - Text(ba.Lstr(resource=self._r + - '.phrase15Text')), # lastly there's bombs + Text( + ba.Lstr(resource=self._r + '.phrase15Text') + ), # lastly there's bombs DelayOld(1900), Text( - ba.Lstr(resource=self._r + - '.phrase16Text')), # throwing bombs takes practice + ba.Lstr(resource=self._r + '.phrase16Text') + ), # throwing bombs takes practice DelayOld(2000), Bomb(), Move(-0.1, -0.1), @@ -2000,12 +2086,13 @@ class TutorialActivity(ba.Activity[Player, Team]): DelayOld(1000), Bomb(), DelayOld(2000), - Text(ba.Lstr(resource=self._r + - '.phrase17Text')), # not a very good throw + Text( + ba.Lstr(resource=self._r + '.phrase17Text') + ), # not a very good throw DelayOld(3000), Text( - ba.Lstr(resource=self._r + - '.phrase18Text')), # moving helps you get distance + ba.Lstr(resource=self._r + '.phrase18Text') + ), # moving helps you get distance DelayOld(1000), Bomb(), DelayOld(500), @@ -2021,8 +2108,9 @@ class TutorialActivity(ba.Activity[Player, Team]): DelayOld(100), Move(0, 0), DelayOld(2500), - Text(ba.Lstr(resource=self._r + - '.phrase19Text')), # jumping helps you get height + Text( + ba.Lstr(resource=self._r + '.phrase19Text') + ), # jumping helps you get height DelayOld(2000), Bomb(), DelayOld(500), @@ -2040,8 +2128,9 @@ class TutorialActivity(ba.Activity[Player, Team]): DelayOld(100), Move(0, 0), DelayOld(2000), - Text(ba.Lstr(resource=self._r + - '.phrase20Text')), # whiplash your bombs + Text( + ba.Lstr(resource=self._r + '.phrase20Text') + ), # whiplash your bombs DelayOld(1000), Bomb(release=False), DelayOld2(80), @@ -2201,23 +2290,29 @@ class TutorialActivity(ba.Activity[Player, Team]): Move(0, 0), DelayOld(2000), AnalyticsScreen('Tutorial Section 7'), - Text(ba.Lstr( - resource=self._r + - '.phrase21Text')), # timing your bombs can be tricky + Text( + ba.Lstr(resource=self._r + '.phrase21Text') + ), # timing your bombs can be tricky Move(-1, 0), DelayOld(1000), Move(0, -0.1), DelayOld(100), Move(0, 0), - SpawnSpaz(0, (-0.7, 4.3, -3.9), - make_current=True, - flash=False, - angle=-30), - SpawnSpaz(1, (6.5, 0, -0.75), - relative_to=0, - make_current=False, - color=(0.3, 0.8, 1.0), - name=ba.Lstr(resource=self._r + '.randomName5Text')), + SpawnSpaz( + 0, + (-0.7, 4.3, -3.9), + make_current=True, + flash=False, + angle=-30, + ), + SpawnSpaz( + 1, + (6.5, 0, -0.75), + relative_to=0, + make_current=False, + color=(0.3, 0.8, 1.0), + name=ba.Lstr(resource=self._r + '.randomName5Text'), + ), DelayOld2(1000), Move(-1, 0), DelayOld2(1800), @@ -2238,8 +2333,9 @@ class TutorialActivity(ba.Activity[Player, Team]): Delay(1500), Text(''), Delay(200), - Text(ba.Lstr(resource=self._r + - '.phrase23Text')), # try cooking off + Text( + ba.Lstr(resource=self._r + '.phrase23Text') + ), # try cooking off Delay(1500), Bomb(), Delay(800), @@ -2253,8 +2349,9 @@ class TutorialActivity(ba.Activity[Player, Team]): Delay(100), Move(0, 0), Delay(2000), - Text(ba.Lstr(resource=self._r + - '.phrase24Text')), # hooray nicely cooked + Text( + ba.Lstr(resource=self._r + '.phrase24Text') + ), # hooray nicely cooked Celebrate(), DelayOld(2000), KillSpaz(1), @@ -2266,17 +2363,19 @@ class TutorialActivity(ba.Activity[Player, Team]): Move(0, 0), DelayOld(1000), AnalyticsScreen('Tutorial Section 8'), - Text(ba.Lstr(resource=self._r + - '.phrase25Text')), # well that's just about it + Text( + ba.Lstr(resource=self._r + '.phrase25Text') + ), # well that's just about it DelayOld(2000), - Text(ba.Lstr(resource=self._r + - '.phrase26Text')), # go get em tiger + Text( + ba.Lstr(resource=self._r + '.phrase26Text') + ), # go get em tiger DelayOld(2000), - Text(ba.Lstr(resource=self._r + - '.phrase27Text')), # remember you training + Text( + ba.Lstr(resource=self._r + '.phrase27Text') + ), # remember you training DelayOld(3000), - Text(ba.Lstr(resource=self._r + - '.phrase28Text')), # well maybe + Text(ba.Lstr(resource=self._r + '.phrase28Text')), # well maybe DelayOld(1600), Text(ba.Lstr(resource=self._r + '.phrase29Text')), # good luck Celebrate('right', duration=10000), @@ -2294,7 +2393,8 @@ class TutorialActivity(ba.Activity[Player, Team]): # Otherwise try again in a few seconds. else: self._read_entries_timer = ba.Timer( - 3.0, ba.WeakCall(self._read_entries)) + 3.0, ba.WeakCall(self._read_entries) + ) def _run_next_entry(self) -> None: @@ -2312,29 +2412,42 @@ class TutorialActivity(ba.Activity[Player, Team]): self._entry_timer = ba.Timer( result, ba.WeakCall(self._run_next_entry), - timeformat=ba.TimeFormat.MILLISECONDS) + timeformat=ba.TimeFormat.MILLISECONDS, + ) return # Done with these entries.. start over soon. - self._read_entries_timer = ba.Timer(1.0, - ba.WeakCall(self._read_entries)) + self._read_entries_timer = ba.Timer( + 1.0, ba.WeakCall(self._read_entries) + ) def _update_skip_votes(self) -> None: count = sum(1 for player in self.players if player.pressed) assert self._skip_count_text - self._skip_count_text.text = ba.Lstr( - resource=self._r + '.skipVoteCountText', - subs=[('${COUNT}', str(count)), - ('${TOTAL}', str(len(self.players)))]) if count > 0 else '' - if (count >= len(self.players) and self.players - and not self._have_skipped): + self._skip_count_text.text = ( + ba.Lstr( + resource=self._r + '.skipVoteCountText', + subs=[ + ('${COUNT}', str(count)), + ('${TOTAL}', str(len(self.players))), + ], + ) + if count > 0 + else '' + ) + if ( + count >= len(self.players) + and self.players + and not self._have_skipped + ): ba.internal.increment_analytics_count('Tutorial skip') ba.set_analytics_screen('Tutorial Skip') self._have_skipped = True ba.playsound(ba.getsound('swish')) # self._skip_count_text.text = self._r.skippingText - self._skip_count_text.text = ba.Lstr(resource=self._r + - '.skippingText') + self._skip_count_text.text = ba.Lstr( + resource=self._r + '.skippingText' + ) assert self._skip_text self._skip_text.text = '' self.end() @@ -2347,21 +2460,25 @@ class TutorialActivity(ba.Activity[Player, Team]): if len(self.players) == 1 and not self._issued_warning: self._issued_warning = True assert self._skip_text - self._skip_text.text = ba.Lstr(resource=self._r + - '.skipConfirmText') + self._skip_text.text = ba.Lstr( + resource=self._r + '.skipConfirmText' + ) self._skip_text.color = (1, 1, 1) self._skip_text.scale = 1.3 incr = 50 t = incr for _i in range(6): - ba.timer(t, - ba.Call(setattr, self._skip_text, 'color', - (1, 0.5, 0.1)), - timeformat=ba.TimeFormat.MILLISECONDS) + ba.timer( + t, + ba.Call(setattr, self._skip_text, 'color', (1, 0.5, 0.1)), + timeformat=ba.TimeFormat.MILLISECONDS, + ) t += incr - ba.timer(t, - ba.Call(setattr, self._skip_text, 'color', (1, 1, 0)), - timeformat=ba.TimeFormat.MILLISECONDS) + ba.timer( + t, + ba.Call(setattr, self._skip_text, 'color', (1, 1, 0)), + timeformat=ba.TimeFormat.MILLISECONDS, + ) t += incr ba.timer(6.0, ba.WeakCall(self._revert_confirm)) return @@ -2370,16 +2487,20 @@ class TutorialActivity(ba.Activity[Player, Team]): # test... if not all(self.players): - ba.print_error('Nonexistent player in _player_pressed_button: ' + - str([str(p) for p in self.players]) + ': we are ' + - str(player)) + ba.print_error( + 'Nonexistent player in _player_pressed_button: ' + + str([str(p) for p in self.players]) + + ': we are ' + + str(player) + ) self._update_skip_votes() def _revert_confirm(self) -> None: assert self._skip_text - self._skip_text.text = ba.Lstr(resource=self._r + - '.toSkipPressAnythingText') + self._skip_text.text = ba.Lstr( + resource=self._r + '.toSkipPressAnythingText' + ) self._skip_text.color = (1, 1, 1) self._issued_warning = False @@ -2388,15 +2509,23 @@ class TutorialActivity(ba.Activity[Player, Team]): # We just wanna know if this player presses anything. player.assigninput( - (ba.InputType.JUMP_PRESS, ba.InputType.PUNCH_PRESS, - ba.InputType.BOMB_PRESS, ba.InputType.PICK_UP_PRESS), - ba.Call(self._player_pressed_button, player)) + ( + ba.InputType.JUMP_PRESS, + ba.InputType.PUNCH_PRESS, + ba.InputType.BOMB_PRESS, + ba.InputType.PICK_UP_PRESS, + ), + ba.Call(self._player_pressed_button, player), + ) def on_player_leave(self, player: Player) -> None: if not all(self.players): - ba.print_error('Nonexistent player in on_player_leave: ' + - str([str(p) for p in self.players]) + ': we are ' + - str(player)) + ba.print_error( + 'Nonexistent player in on_player_leave: ' + + str([str(p) for p in self.players]) + + ': we are ' + + str(player) + ) super().on_player_leave(player) # our leaving may influence the vote total needed/etc self._update_skip_votes() diff --git a/dist/ba_data/python/bastd/ui/account/__init__.py b/dist/ba_data/python/bastd/ui/account/__init__.py index 24e4007..b4ea7e2 100644 --- a/dist/ba_data/python/bastd/ui/account/__init__.py +++ b/dist/ba_data/python/bastd/ui/account/__init__.py @@ -12,18 +12,22 @@ def show_sign_in_prompt(account_type: str | None = None) -> None: from bastd.ui.confirm import ConfirmWindow from bastd.ui.account import settings from ba.internal import sign_in_v1 + if account_type == 'Google Play': ConfirmWindow( ba.Lstr(resource='notSignedInGooglePlayErrorText'), lambda: sign_in_v1('Google Play'), ok_text=ba.Lstr(resource='accountSettingsWindow.signInText'), width=460, - height=130) + height=130, + ) else: ConfirmWindow( ba.Lstr(resource='notSignedInErrorText'), - lambda: settings.AccountSettingsWindow(modal=True, - close_once_signed_in=True), + lambda: settings.AccountSettingsWindow( + modal=True, close_once_signed_in=True + ), ok_text=ba.Lstr(resource='accountSettingsWindow.signInText'), width=460, - height=130) + height=130, + ) diff --git a/dist/ba_data/python/bastd/ui/account/link.py b/dist/ba_data/python/bastd/ui/account/link.py index b16f839..7309a79 100644 --- a/dist/ba_data/python/bastd/ui/account/link.py +++ b/dist/ba_data/python/bastd/ui/account/link.py @@ -32,81 +32,111 @@ class AccountLinkWindow(ba.Window): self._width = 560 self._height = 420 uiscale = ba.app.ui.uiscale - base_scale = (1.65 if uiscale is ba.UIScale.SMALL else - 1.5 if uiscale is ba.UIScale.MEDIUM else 1.1) - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height), - transition=transition, - scale=base_scale, - scale_origin_stack_offset=scale_origin, - stack_offset=(0, -10) if uiscale is ba.UIScale.SMALL else (0, 0))) - self._cancel_button = ba.buttonwidget(parent=self._root_widget, - position=(40, self._height - 45), - size=(50, 50), - scale=0.7, - label='', - color=bg_color, - on_activate_call=self._cancel, - autoselect=True, - icon=ba.gettexture('crossOut'), - iconscale=1.2) + base_scale = ( + 1.65 + if uiscale is ba.UIScale.SMALL + else 1.5 + if uiscale is ba.UIScale.MEDIUM + else 1.1 + ) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height), + transition=transition, + scale=base_scale, + scale_origin_stack_offset=scale_origin, + stack_offset=(0, -10) + if uiscale is ba.UIScale.SMALL + else (0, 0), + ) + ) + self._cancel_button = ba.buttonwidget( + parent=self._root_widget, + position=(40, self._height - 45), + size=(50, 50), + scale=0.7, + label='', + color=bg_color, + on_activate_call=self._cancel, + autoselect=True, + icon=ba.gettexture('crossOut'), + iconscale=1.2, + ) maxlinks = ba.internal.get_v1_account_misc_read_val( - 'maxLinkAccounts', 5) + 'maxLinkAccounts', 5 + ) ba.textwidget( parent=self._root_widget, position=(self._width * 0.5, self._height * 0.56), size=(0, 0), - text=ba.Lstr(resource=( - 'accountSettingsWindow.linkAccountsInstructionsNewText'), - subs=[('${COUNT}', str(maxlinks))]), + text=ba.Lstr( + resource=( + 'accountSettingsWindow.linkAccountsInstructionsNewText' + ), + subs=[('${COUNT}', str(maxlinks))], + ), maxwidth=self._width * 0.9, color=ba.app.ui.infotextcolor, max_height=self._height * 0.6, h_align='center', - v_align='center') - ba.containerwidget(edit=self._root_widget, - cancel_button=self._cancel_button) + v_align='center', + ) + ba.containerwidget( + edit=self._root_widget, cancel_button=self._cancel_button + ) ba.buttonwidget( parent=self._root_widget, position=(40, 30), size=(200, 60), label=ba.Lstr( - resource='accountSettingsWindow.linkAccountsGenerateCodeText'), + resource='accountSettingsWindow.linkAccountsGenerateCodeText' + ), autoselect=True, - on_activate_call=self._generate_press) + on_activate_call=self._generate_press, + ) self._enter_code_button = ba.buttonwidget( parent=self._root_widget, position=(self._width - 240, 30), size=(200, 60), label=ba.Lstr( - resource='accountSettingsWindow.linkAccountsEnterCodeText'), + resource='accountSettingsWindow.linkAccountsEnterCodeText' + ), autoselect=True, - on_activate_call=self._enter_code_press) + on_activate_call=self._enter_code_press, + ) def _generate_press(self) -> None: from bastd.ui import account + if ba.internal.get_v1_account_state() != 'signed_in': account.show_sign_in_prompt() return ba.screenmessage( ba.Lstr(resource='gatherWindow.requestingAPromoCodeText'), - color=(0, 1, 0)) - ba.internal.add_transaction({ - 'type': 'ACCOUNT_LINK_CODE_REQUEST', - 'expire_time': time.time() + 5 - }) + color=(0, 1, 0), + ) + ba.internal.add_transaction( + { + 'type': 'ACCOUNT_LINK_CODE_REQUEST', + 'expire_time': time.time() + 5, + } + ) ba.internal.run_transactions() def _enter_code_press(self) -> None: from bastd.ui import promocode - promocode.PromoCodeWindow(modal=True, - origin_widget=self._enter_code_button) - ba.containerwidget(edit=self._root_widget, - transition=self._transition_out) + + promocode.PromoCodeWindow( + modal=True, origin_widget=self._enter_code_button + ) + ba.containerwidget( + edit=self._root_widget, transition=self._transition_out + ) def _cancel(self) -> None: - ba.containerwidget(edit=self._root_widget, - transition=self._transition_out) + ba.containerwidget( + edit=self._root_widget, transition=self._transition_out + ) class AccountLinkCodeWindow(ba.Window): @@ -116,36 +146,49 @@ class AccountLinkCodeWindow(ba.Window): self._width = 350 self._height = 200 uiscale = ba.app.ui.uiscale - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height), - color=(0.45, 0.63, 0.15), - transition='in_scale', - scale=(1.8 if uiscale is ba.UIScale.SMALL else - 1.35 if uiscale is ba.UIScale.MEDIUM else 1.0))) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height), + color=(0.45, 0.63, 0.15), + transition='in_scale', + scale=( + 1.8 + if uiscale is ba.UIScale.SMALL + else 1.35 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + ) + ) self._data = copy.deepcopy(data) ba.playsound(ba.getsound('cashRegister')) ba.playsound(ba.getsound('swish')) - self._cancel_button = ba.buttonwidget(parent=self._root_widget, - scale=0.5, - position=(40, self._height - 40), - size=(50, 50), - label='', - on_activate_call=self.close, - autoselect=True, - color=(0.45, 0.63, 0.15), - icon=ba.gettexture('crossOut'), - iconscale=1.2) - ba.containerwidget(edit=self._root_widget, - cancel_button=self._cancel_button) - ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, self._height * 0.5), - size=(0, 0), - color=(1.0, 3.0, 1.0), - scale=2.0, - h_align='center', - v_align='center', - text=data['code'], - maxwidth=self._width * 0.85) + self._cancel_button = ba.buttonwidget( + parent=self._root_widget, + scale=0.5, + position=(40, self._height - 40), + size=(50, 50), + label='', + on_activate_call=self.close, + autoselect=True, + color=(0.45, 0.63, 0.15), + icon=ba.gettexture('crossOut'), + iconscale=1.2, + ) + ba.containerwidget( + edit=self._root_widget, cancel_button=self._cancel_button + ) + ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, self._height * 0.5), + size=(0, 0), + color=(1.0, 3.0, 1.0), + scale=2.0, + h_align='center', + v_align='center', + text=data['code'], + maxwidth=self._width * 0.85, + ) def close(self) -> None: """close the window""" diff --git a/dist/ba_data/python/bastd/ui/account/settings.py b/dist/ba_data/python/bastd/ui/account/settings.py index a75eb0f..3aae991 100644 --- a/dist/ba_data/python/bastd/ui/account/settings.py +++ b/dist/ba_data/python/bastd/ui/account/settings.py @@ -18,11 +18,13 @@ if TYPE_CHECKING: class AccountSettingsWindow(ba.Window): """Window for account related functionality.""" - def __init__(self, - transition: str = 'in_right', - modal: bool = False, - origin_widget: ba.Widget | None = None, - close_once_signed_in: bool = False): + def __init__( + self, + transition: str = 'in_right', + modal: bool = False, + origin_widget: ba.Widget | None = None, + close_once_signed_in: bool = False, + ): # pylint: disable=too-many-statements self._sign_in_v2_button: ba.Widget | None = None @@ -44,15 +46,20 @@ class AccountSettingsWindow(ba.Window): self._r = 'accountSettingsWindow' self._modal = modal self._needs_refresh = False - self._signed_in = (ba.internal.get_v1_account_state() == 'signed_in') + self._signed_in = ba.internal.get_v1_account_state() == 'signed_in' self._account_state_num = ba.internal.get_v1_account_state_num() - self._show_linked = (self._signed_in - and ba.internal.get_v1_account_misc_read_val( - 'allowAccountLinking2', False)) - self._check_sign_in_timer = ba.Timer(1.0, - ba.WeakCall(self._update), - timetype=ba.TimeType.REAL, - repeat=True) + self._show_linked = ( + self._signed_in + and ba.internal.get_v1_account_misc_read_val( + 'allowAccountLinking2', False + ) + ) + self._check_sign_in_timer = ba.Timer( + 1.0, + ba.WeakCall(self._update), + timetype=ba.TimeType.REAL, + repeat=True, + ) # Currently we can only reset achievements on game-center. account_type: str | None @@ -60,15 +67,20 @@ class AccountSettingsWindow(ba.Window): account_type = ba.internal.get_v1_account_type() else: account_type = None - self._can_reset_achievements = (account_type == 'Game Center') + self._can_reset_achievements = account_type == 'Game Center' app = ba.app uiscale = app.ui.uiscale self._width = 760 if uiscale is ba.UIScale.SMALL else 660 x_offs = 50 if uiscale is ba.UIScale.SMALL else 0 - self._height = (390 if uiscale is ba.UIScale.SMALL else - 430 if uiscale is ba.UIScale.MEDIUM else 490) + self._height = ( + 390 + if uiscale is ba.UIScale.SMALL + else 430 + if uiscale is ba.UIScale.MEDIUM + else 490 + ) self._sign_in_button = None self._sign_in_text = None @@ -92,18 +104,29 @@ class AccountSettingsWindow(ba.Window): self._show_sign_in_buttons.append('V2') top_extra = 15 if uiscale is ba.UIScale.SMALL else 0 - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height + top_extra), - transition=transition, - toolbar_visibility='menu_minimal', - scale_origin_stack_offset=scale_origin, - scale=(2.09 if uiscale is ba.UIScale.SMALL else - 1.4 if uiscale is ba.UIScale.MEDIUM else 1.0), - stack_offset=(0, -19) if uiscale is ba.UIScale.SMALL else (0, 0))) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height + top_extra), + transition=transition, + toolbar_visibility='menu_minimal', + scale_origin_stack_offset=scale_origin, + scale=( + 2.09 + if uiscale is ba.UIScale.SMALL + else 1.4 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + stack_offset=(0, -19) + if uiscale is ba.UIScale.SMALL + else (0, 0), + ) + ) if uiscale is ba.UIScale.SMALL and ba.app.ui.use_toolbars: self._back_button = None - ba.containerwidget(edit=self._root_widget, - on_cancel_call=self._back) + ba.containerwidget( + edit=self._root_widget, on_cancel_call=self._back + ) else: self._back_button = btn = ba.buttonwidget( parent=self._root_widget, @@ -113,34 +136,43 @@ class AccountSettingsWindow(ba.Window): text_scale=1.2, autoselect=True, label=ba.Lstr( - resource='doneText' if self._modal else 'backText'), + resource='doneText' if self._modal else 'backText' + ), button_type='regular' if self._modal else 'back', - on_activate_call=self._back) + on_activate_call=self._back, + ) ba.containerwidget(edit=self._root_widget, cancel_button=btn) if not self._modal: - ba.buttonwidget(edit=btn, - button_type='backSmall', - size=(60, 56), - label=ba.charstr(ba.SpecialChar.BACK)) + ba.buttonwidget( + edit=btn, + button_type='backSmall', + size=(60, 56), + label=ba.charstr(ba.SpecialChar.BACK), + ) - ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, self._height - 41), - size=(0, 0), - text=ba.Lstr(resource=self._r + '.titleText'), - color=ba.app.ui.title_color, - maxwidth=self._width - 340, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, self._height - 41), + size=(0, 0), + text=ba.Lstr(resource=self._r + '.titleText'), + color=ba.app.ui.title_color, + maxwidth=self._width - 340, + h_align='center', + v_align='center', + ) self._scrollwidget = ba.scrollwidget( parent=self._root_widget, highlight=False, - position=((self._width - self._scroll_width) * 0.5, - self._height - 65 - self._scroll_height), + position=( + (self._width - self._scroll_width) * 0.5, + self._height - 65 - self._scroll_height, + ), size=(self._scroll_width, self._scroll_height), claims_left_right=True, claims_tab=True, - selection_loops_to_parent=True) + selection_loops_to_parent=True, + ) self._subcontainer: ba.Widget | None = None self._refresh() self._restore_state() @@ -158,15 +190,21 @@ class AccountSettingsWindow(ba.Window): account_state_num = ba.internal.get_v1_account_state_num() account_state = ba.internal.get_v1_account_state() - show_linked = (self._signed_in - and ba.internal.get_v1_account_misc_read_val( - 'allowAccountLinking2', False)) + show_linked = ( + self._signed_in + and ba.internal.get_v1_account_misc_read_val( + 'allowAccountLinking2', False + ) + ) - if (account_state_num != self._account_state_num - or self._show_linked != show_linked or self._needs_refresh): + if ( + account_state_num != self._account_state_num + or self._show_linked != show_linked + or self._needs_refresh + ): self._show_linked = show_linked self._account_state_num = account_state_num - self._signed_in = (account_state == 'signed_in') + self._signed_in = account_state == 'signed_in' self._refresh() # Go ahead and refresh some individual things @@ -189,8 +227,11 @@ class AccountSettingsWindow(ba.Window): from bastd.ui import confirm account_state = ba.internal.get_v1_account_state() - account_type = (ba.internal.get_v1_account_type() - if account_state == 'signed_in' else 'unknown') + account_type = ( + ba.internal.get_v1_account_type() + if account_state == 'signed_in' + else 'unknown' + ) is_google = account_type == 'Google Play' @@ -206,34 +247,47 @@ class AccountSettingsWindow(ba.Window): show_signing_in_text = account_state == 'signing_in' signing_in_text_space = 80.0 - show_google_play_sign_in_button = (account_state == 'signed_out' - and 'Google Play' - in self._show_sign_in_buttons) - show_device_sign_in_button = (account_state == 'signed_out' and 'Local' - in self._show_sign_in_buttons) - show_v2_sign_in_button = (account_state == 'signed_out' - and 'V2' in self._show_sign_in_buttons) + show_google_play_sign_in_button = ( + account_state == 'signed_out' + and 'Google Play' in self._show_sign_in_buttons + ) + show_device_sign_in_button = ( + account_state == 'signed_out' + and 'Local' in self._show_sign_in_buttons + ) + show_v2_sign_in_button = ( + account_state == 'signed_out' and 'V2' in self._show_sign_in_buttons + ) sign_in_button_space = 70.0 - show_game_service_button = (self._signed_in - and account_type in ['Game Center']) + show_game_service_button = self._signed_in and account_type in [ + 'Game Center' + ] game_service_button_space = 60.0 - show_linked_accounts_text = (self._signed_in and - ba.internal.get_v1_account_misc_read_val( - 'allowAccountLinking2', False)) + show_linked_accounts_text = ( + self._signed_in + and ba.internal.get_v1_account_misc_read_val( + 'allowAccountLinking2', False + ) + ) linked_accounts_text_space = 60.0 - show_achievements_button = (self._signed_in and account_type - in ('Google Play', 'Alibaba', 'Local', - 'OUYA', 'V2')) + show_achievements_button = self._signed_in and account_type in ( + 'Google Play', + 'Alibaba', + 'Local', + 'OUYA', + 'V2', + ) achievements_button_space = 60.0 - show_achievements_text = (self._signed_in - and not show_achievements_button) + show_achievements_text = ( + self._signed_in and not show_achievements_button + ) achievements_text_space = 27.0 - show_leaderboards_button = (self._signed_in and is_google) + show_leaderboards_button = self._signed_in and is_google leaderboards_button_space = 60.0 show_campaign_progress = self._signed_in @@ -245,30 +299,38 @@ class AccountSettingsWindow(ba.Window): show_reset_progress_button = False reset_progress_button_space = 70.0 - show_manage_v2_account_button = (self._signed_in - and account_type == 'V2' - and bool(False)) # Disabled for now. + show_manage_v2_account_button = ( + self._signed_in and account_type == 'V2' and bool(False) + ) # Disabled for now. manage_v2_account_button_space = 100.0 show_player_profiles_button = self._signed_in - player_profiles_button_space = (70.0 if show_manage_v2_account_button - else 100.0) + player_profiles_button_space = ( + 70.0 if show_manage_v2_account_button else 100.0 + ) - show_link_accounts_button = (self._signed_in and - ba.internal.get_v1_account_misc_read_val( - 'allowAccountLinking2', False)) + show_link_accounts_button = ( + self._signed_in + and ba.internal.get_v1_account_misc_read_val( + 'allowAccountLinking2', False + ) + ) link_accounts_button_space = 70.0 show_unlink_accounts_button = show_link_accounts_button unlink_accounts_button_space = 90.0 - show_sign_out_button = (self._signed_in and account_type - in ['Local', 'Google Play', 'V2']) + show_sign_out_button = self._signed_in and account_type in [ + 'Local', + 'Google Play', + 'V2', + ] sign_out_button_space = 70.0 show_cancel_v2_sign_in_button = ( account_state == 'signing_in' - and ba.app.accounts_v2.have_primary_credentials()) + and ba.app.accounts_v2.have_primary_credentials() + ) cancel_v2_sign_in_button_space = 70.0 if self._subcontainer is not None: @@ -316,13 +378,14 @@ class AccountSettingsWindow(ba.Window): self._sub_height += sign_out_button_space if show_cancel_v2_sign_in_button: self._sub_height += cancel_v2_sign_in_button_space - self._subcontainer = ba.containerwidget(parent=self._scrollwidget, - size=(self._sub_width, - self._sub_height), - background=False, - claims_left_right=True, - claims_tab=True, - selection_loops_to_parent=True) + self._subcontainer = ba.containerwidget( + parent=self._scrollwidget, + size=(self._sub_width, self._sub_height), + background=False, + claims_left_right=True, + claims_tab=True, + selection_loops_to_parent=True, + ) first_selectable = None v = self._sub_height - 10.0 @@ -335,14 +398,17 @@ class AccountSettingsWindow(ba.Window): size=(0, 0), text=ba.Lstr( resource='accountSettingsWindow.deviceSpecificAccountText', - subs=[('${NAME}', - ba.internal.get_v1_account_display_string())]), + subs=[ + ('${NAME}', ba.internal.get_v1_account_display_string()) + ], + ), scale=0.7, color=(0.5, 0.5, 0.6), maxwidth=self._sub_width * 0.9, flatness=1.0, h_align='center', - v_align='center') + v_align='center', + ) v -= local_signed_in_as_space * 0.4 self._account_name_text: ba.Widget | None @@ -350,16 +416,19 @@ class AccountSettingsWindow(ba.Window): v -= signed_in_as_space * 0.2 txt = ba.Lstr( resource='accountSettingsWindow.youAreSignedInAsText', - fallback_resource='accountSettingsWindow.youAreLoggedInAsText') - ba.textwidget(parent=self._subcontainer, - position=(self._sub_width * 0.5, v), - size=(0, 0), - text=txt, - scale=0.9, - color=ba.app.ui.title_color, - maxwidth=self._sub_width * 0.9, - h_align='center', - v_align='center') + fallback_resource='accountSettingsWindow.youAreLoggedInAsText', + ) + ba.textwidget( + parent=self._subcontainer, + position=(self._sub_width * 0.5, v), + size=(0, 0), + text=txt, + scale=0.9, + color=ba.app.ui.title_color, + maxwidth=self._sub_width * 0.9, + h_align='center', + v_align='center', + ) v -= signed_in_as_space * 0.4 self._account_name_text = ba.textwidget( parent=self._subcontainer, @@ -370,7 +439,8 @@ class AccountSettingsWindow(ba.Window): res_scale=1.5, color=(1, 1, 1, 1), h_align='center', - v_align='center') + v_align='center', + ) self._refresh_account_name_text() v -= signed_in_as_space * 0.4 else: @@ -385,45 +455,55 @@ class AccountSettingsWindow(ba.Window): v -= sign_in_benefits_space app = ba.app extra: str | ba.Lstr | None - if (app.platform in ['mac', 'ios'] - and app.subplatform == 'appstore'): + if app.platform in ['mac', 'ios'] and app.subplatform == 'appstore': extra = ba.Lstr( value='\n${S}', - subs=[('${S}', - ba.Lstr(resource='signInWithGameCenterText'))]) + subs=[ + ('${S}', ba.Lstr(resource='signInWithGameCenterText')) + ], + ) else: extra = '' - ba.textwidget(parent=self._subcontainer, - position=(self._sub_width * 0.5, - v + sign_in_benefits_space * 0.4), - size=(0, 0), - text=ba.Lstr(value='${A}${B}', - subs=[('${A}', - ba.Lstr(resource=self._r + - '.signInInfoText')), - ('${B}', extra)]), - max_height=sign_in_benefits_space * 0.9, - scale=0.9, - color=(0.75, 0.7, 0.8), - maxwidth=self._sub_width * 0.8, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._subcontainer, + position=( + self._sub_width * 0.5, + v + sign_in_benefits_space * 0.4, + ), + size=(0, 0), + text=ba.Lstr( + value='${A}${B}', + subs=[ + ('${A}', ba.Lstr(resource=self._r + '.signInInfoText')), + ('${B}', extra), + ], + ), + max_height=sign_in_benefits_space * 0.9, + scale=0.9, + color=(0.75, 0.7, 0.8), + maxwidth=self._sub_width * 0.8, + h_align='center', + v_align='center', + ) if show_signing_in_text: v -= signing_in_text_space ba.textwidget( parent=self._subcontainer, - position=(self._sub_width * 0.5, - v + signing_in_text_space * 0.5), + position=( + self._sub_width * 0.5, + v + signing_in_text_space * 0.5, + ), size=(0, 0), text=ba.Lstr(resource='accountSettingsWindow.signingInText'), scale=0.9, color=(0, 1, 0), maxwidth=self._sub_width * 0.8, h_align='center', - v_align='center') + v_align='center', + ) if show_google_play_sign_in_button: button_width = 350 @@ -435,18 +515,28 @@ class AccountSettingsWindow(ba.Window): size=(button_width, 60), label=ba.Lstr( value='${A}${B}', - subs=[('${A}', - ba.charstr(ba.SpecialChar.GOOGLE_PLAY_GAMES_LOGO)), - ('${B}', - ba.Lstr(resource=self._r + - '.signInWithGooglePlayText'))]), - on_activate_call=lambda: self._sign_in_press('Google Play')) + subs=[ + ( + '${A}', + ba.charstr(ba.SpecialChar.GOOGLE_PLAY_GAMES_LOGO), + ), + ( + '${B}', + ba.Lstr( + resource=self._r + '.signInWithGooglePlayText' + ), + ), + ], + ), + on_activate_call=lambda: self._sign_in_press('Google Play'), + ) if first_selectable is None: first_selectable = btn if ba.app.ui.use_toolbars: - ba.widget(edit=btn, - right_widget=ba.internal.get_special_widget( - 'party_button')) + ba.widget( + edit=btn, + right_widget=ba.internal.get_special_widget('party_button'), + ) ba.widget(edit=btn, left_widget=bbtn) ba.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100) self._sign_in_text = None @@ -460,7 +550,8 @@ class AccountSettingsWindow(ba.Window): autoselect=True, size=(button_width, 60), label='', - on_activate_call=self._v2_sign_in_press) + on_activate_call=self._v2_sign_in_press, + ) ba.textwidget( parent=self._subcontainer, draw_controller=btn, @@ -470,29 +561,37 @@ class AccountSettingsWindow(ba.Window): position=(self._sub_width * 0.5, v + 17), text=ba.Lstr( value='${A}${B}', - subs=[('${A}', ba.charstr(ba.SpecialChar.V2_LOGO)), - ('${B}', - ba.Lstr(resource=self._r + '.signInWithV2Text'))]), + subs=[ + ('${A}', ba.charstr(ba.SpecialChar.V2_LOGO)), + ( + '${B}', + ba.Lstr(resource=self._r + '.signInWithV2Text'), + ), + ], + ), maxwidth=button_width * 0.8, - color=(0.75, 1.0, 0.7)) - ba.textwidget(parent=self._subcontainer, - draw_controller=btn, - h_align='center', - v_align='center', - size=(0, 0), - position=(self._sub_width * 0.5, v - 4), - text=ba.Lstr(resource=self._r + - '.signInWithV2InfoText'), - flatness=1.0, - scale=0.57, - maxwidth=button_width * 0.9, - color=(0.55, 0.8, 0.5)) + color=(0.75, 1.0, 0.7), + ) + ba.textwidget( + parent=self._subcontainer, + draw_controller=btn, + h_align='center', + v_align='center', + size=(0, 0), + position=(self._sub_width * 0.5, v - 4), + text=ba.Lstr(resource=self._r + '.signInWithV2InfoText'), + flatness=1.0, + scale=0.57, + maxwidth=button_width * 0.9, + color=(0.55, 0.8, 0.5), + ) if first_selectable is None: first_selectable = btn if ba.app.ui.use_toolbars: - ba.widget(edit=btn, - right_widget=ba.internal.get_special_widget( - 'party_button')) + ba.widget( + edit=btn, + right_widget=ba.internal.get_special_widget('party_button'), + ) ba.widget(edit=btn, left_widget=bbtn) ba.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100) self._sign_in_text = None @@ -506,40 +605,48 @@ class AccountSettingsWindow(ba.Window): autoselect=True, size=(button_width, 60), label='', - on_activate_call=lambda: self._sign_in_press('Local')) - ba.textwidget(parent=self._subcontainer, - draw_controller=btn, - h_align='center', - v_align='center', - size=(0, 0), - position=(self._sub_width * 0.5, v + 17), - text=ba.Lstr( - value='${A}${B}', - subs=[('${A}', - ba.charstr(ba.SpecialChar.LOCAL_ACCOUNT)), - ('${B}', - ba.Lstr(resource=self._r + - '.signInWithDeviceText'))]), - maxwidth=button_width * 0.8, - color=(0.75, 1.0, 0.7)) - ba.textwidget(parent=self._subcontainer, - draw_controller=btn, - h_align='center', - v_align='center', - size=(0, 0), - position=(self._sub_width * 0.5, v - 4), - text=ba.Lstr(resource=self._r + - '.signInWithDeviceInfoText'), - flatness=1.0, - scale=0.57, - maxwidth=button_width * 0.9, - color=(0.55, 0.8, 0.5)) + on_activate_call=lambda: self._sign_in_press('Local'), + ) + ba.textwidget( + parent=self._subcontainer, + draw_controller=btn, + h_align='center', + v_align='center', + size=(0, 0), + position=(self._sub_width * 0.5, v + 17), + text=ba.Lstr( + value='${A}${B}', + subs=[ + ('${A}', ba.charstr(ba.SpecialChar.LOCAL_ACCOUNT)), + ( + '${B}', + ba.Lstr(resource=self._r + '.signInWithDeviceText'), + ), + ], + ), + maxwidth=button_width * 0.8, + color=(0.75, 1.0, 0.7), + ) + ba.textwidget( + parent=self._subcontainer, + draw_controller=btn, + h_align='center', + v_align='center', + size=(0, 0), + position=(self._sub_width * 0.5, v - 4), + text=ba.Lstr(resource=self._r + '.signInWithDeviceInfoText'), + flatness=1.0, + scale=0.57, + maxwidth=button_width * 0.9, + color=(0.55, 0.8, 0.5), + ) if first_selectable is None: first_selectable = btn if ba.app.ui.use_toolbars: - ba.widget(edit=btn, - right_widget=ba.internal.get_special_widget( - 'party_button')) + ba.widget( + edit=btn, + right_widget=ba.internal.get_special_widget('party_button'), + ) ba.widget(edit=btn, left_widget=bbtn) ba.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100) self._sign_in_text = None @@ -552,18 +659,21 @@ class AccountSettingsWindow(ba.Window): position=((self._sub_width - button_width) * 0.5, v + 30), autoselect=True, size=(button_width, 60), - label=ba.Lstr(resource=self._r + '.manageAccount'), + label=ba.Lstr(resource=self._r + '.manageAccountText'), color=(0.55, 0.5, 0.6), icon=ba.gettexture('settingsIcon'), textcolor=(0.75, 0.7, 0.8), on_activate_call=lambda: ba.open_url( - 'https://ballistica.net/accountsettings')) + 'https://ballistica.net/accountsettings' + ), + ) if first_selectable is None: first_selectable = btn if ba.app.ui.use_toolbars: - ba.widget(edit=btn, - right_widget=ba.internal.get_special_widget( - 'party_button')) + ba.widget( + edit=btn, + right_widget=ba.internal.get_special_widget('party_button'), + ) ba.widget(edit=btn, left_widget=bbtn) if show_player_profiles_button: @@ -578,13 +688,15 @@ class AccountSettingsWindow(ba.Window): color=(0.55, 0.5, 0.6), icon=ba.gettexture('cuteSpaz'), textcolor=(0.75, 0.7, 0.8), - on_activate_call=self._player_profiles_press) + on_activate_call=self._player_profiles_press, + ) if first_selectable is None: first_selectable = btn if ba.app.ui.use_toolbars: - ba.widget(edit=btn, - right_widget=ba.internal.get_special_widget( - 'party_button')) + ba.widget( + edit=btn, + right_widget=ba.internal.get_special_widget('party_button'), + ) ba.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=0) # the button to go to OS-Specific leaderboards/high-score-lists/etc. @@ -595,8 +707,9 @@ class AccountSettingsWindow(ba.Window): if account_type == 'Game Center': account_type_name = ba.Lstr(resource='gameCenterText') else: - raise ValueError("unknown account type: '" + - str(account_type) + "'") + raise ValueError( + "unknown account type: '" + str(account_type) + "'" + ) self._game_service_button = btn = ba.buttonwidget( parent=self._subcontainer, position=((self._sub_width - button_width) * 0.5, v), @@ -605,13 +718,15 @@ class AccountSettingsWindow(ba.Window): autoselect=True, on_activate_call=ba.internal.show_online_score_ui, size=(button_width, 50), - label=account_type_name) + label=account_type_name, + ) if first_selectable is None: first_selectable = btn if ba.app.ui.use_toolbars: - ba.widget(edit=btn, - right_widget=ba.internal.get_special_widget( - 'party_button')) + ba.widget( + edit=btn, + right_widget=ba.internal.get_special_widget('party_button'), + ) ba.widget(edit=btn, left_widget=bbtn) v -= game_service_button_space * 0.15 else: @@ -628,7 +743,8 @@ class AccountSettingsWindow(ba.Window): color=(0.75, 0.7, 0.8), maxwidth=self._sub_width * 0.8, h_align='center', - v_align='center') + v_align='center', + ) v -= achievements_text_space * 0.5 else: self._achievements_text = None @@ -643,18 +759,23 @@ class AccountSettingsWindow(ba.Window): color=(0.55, 0.5, 0.6), textcolor=(0.75, 0.7, 0.8), autoselect=True, - icon=ba.gettexture('googlePlayAchievementsIcon' - if is_google else 'achievementsIcon'), + icon=ba.gettexture( + 'googlePlayAchievementsIcon' + if is_google + else 'achievementsIcon' + ), icon_color=(0.8, 0.95, 0.7) if is_google else (0.85, 0.8, 0.9), on_activate_call=self._on_achievements_press, size=(button_width, 50), - label='') + label='', + ) if first_selectable is None: first_selectable = btn if ba.app.ui.use_toolbars: - ba.widget(edit=btn, - right_widget=ba.internal.get_special_widget( - 'party_button')) + ba.widget( + edit=btn, + right_widget=ba.internal.get_special_widget('party_button'), + ) ba.widget(edit=btn, left_widget=bbtn) v -= achievements_button_space * 0.15 else: @@ -677,13 +798,15 @@ class AccountSettingsWindow(ba.Window): icon_color=(0.8, 0.95, 0.7), on_activate_call=self._on_leaderboards_press, size=(button_width, 50), - label=ba.Lstr(resource='leaderboardsText')) + label=ba.Lstr(resource='leaderboardsText'), + ) if first_selectable is None: first_selectable = btn if ba.app.ui.use_toolbars: - ba.widget(edit=btn, - right_widget=ba.internal.get_special_widget( - 'party_button')) + ba.widget( + edit=btn, + right_widget=ba.internal.get_special_widget('party_button'), + ) ba.widget(edit=btn, left_widget=bbtn) v -= leaderboards_button_space * 0.15 else: @@ -700,7 +823,8 @@ class AccountSettingsWindow(ba.Window): color=(0.75, 0.7, 0.8), maxwidth=self._sub_width * 0.8, h_align='center', - v_align='center') + v_align='center', + ) v -= campaign_progress_space * 0.5 self._refresh_campaign_progress_text() else: @@ -709,16 +833,17 @@ class AccountSettingsWindow(ba.Window): self._tickets_text: ba.Widget | None if show_tickets: v -= tickets_space * 0.5 - self._tickets_text = ba.textwidget(parent=self._subcontainer, - position=(self._sub_width * 0.5, - v), - size=(0, 0), - scale=0.9, - color=(0.75, 0.7, 0.8), - maxwidth=self._sub_width * 0.8, - flatness=1.0, - h_align='center', - v_align='center') + self._tickets_text = ba.textwidget( + parent=self._subcontainer, + position=(self._sub_width * 0.5, v), + size=(0, 0), + scale=0.9, + color=(0.75, 0.7, 0.8), + maxwidth=self._sub_width * 0.8, + flatness=1.0, + h_align='center', + v_align='center', + ) v -= tickets_space * 0.5 self._refresh_tickets_text() @@ -730,11 +855,13 @@ class AccountSettingsWindow(ba.Window): button_width = 250 if show_reset_progress_button: - confirm_text = (ba.Lstr(resource=self._r + - '.resetProgressConfirmText') - if self._can_reset_achievements else ba.Lstr( - resource=self._r + - '.resetProgressConfirmNoAchievementsText')) + confirm_text = ( + ba.Lstr(resource=self._r + '.resetProgressConfirmText') + if self._can_reset_achievements + else ba.Lstr( + resource=self._r + '.resetProgressConfirmNoAchievementsText' + ) + ) v -= reset_progress_button_space self._reset_progress_button = btn = ba.buttonwidget( parent=self._subcontainer, @@ -748,13 +875,16 @@ class AccountSettingsWindow(ba.Window): text=confirm_text, width=500, height=200, - action=self._reset_progress)) + action=self._reset_progress, + ), + ) if first_selectable is None: first_selectable = btn if ba.app.ui.use_toolbars: - ba.widget(edit=btn, - right_widget=ba.internal.get_special_widget( - 'party_button')) + ba.widget( + edit=btn, + right_widget=ba.internal.get_special_widget('party_button'), + ) ba.widget(edit=btn, left_widget=bbtn) self._linked_accounts_text: ba.Widget | None @@ -768,7 +898,8 @@ class AccountSettingsWindow(ba.Window): color=(0.75, 0.7, 0.8), maxwidth=self._sub_width * 0.95, h_align='center', - v_align='center') + v_align='center', + ) v -= linked_accounts_text_space * 0.2 self._update_linked_accounts_text() else: @@ -783,34 +914,39 @@ class AccountSettingsWindow(ba.Window): size=(button_width, 60), label='', color=(0.55, 0.5, 0.6), - on_activate_call=self._link_accounts_press) - ba.textwidget(parent=self._subcontainer, - draw_controller=btn, - h_align='center', - v_align='center', - size=(0, 0), - position=(self._sub_width * 0.5, v + 17 + 20), - text=ba.Lstr(resource=self._r + '.linkAccountsText'), - maxwidth=button_width * 0.8, - color=(0.75, 0.7, 0.8)) - ba.textwidget(parent=self._subcontainer, - draw_controller=btn, - h_align='center', - v_align='center', - size=(0, 0), - position=(self._sub_width * 0.5, v - 4 + 20), - text=ba.Lstr(resource=self._r + - '.linkAccountsInfoText'), - flatness=1.0, - scale=0.5, - maxwidth=button_width * 0.8, - color=(0.75, 0.7, 0.8)) + on_activate_call=self._link_accounts_press, + ) + ba.textwidget( + parent=self._subcontainer, + draw_controller=btn, + h_align='center', + v_align='center', + size=(0, 0), + position=(self._sub_width * 0.5, v + 17 + 20), + text=ba.Lstr(resource=self._r + '.linkAccountsText'), + maxwidth=button_width * 0.8, + color=(0.75, 0.7, 0.8), + ) + ba.textwidget( + parent=self._subcontainer, + draw_controller=btn, + h_align='center', + v_align='center', + size=(0, 0), + position=(self._sub_width * 0.5, v - 4 + 20), + text=ba.Lstr(resource=self._r + '.linkAccountsInfoText'), + flatness=1.0, + scale=0.5, + maxwidth=button_width * 0.8, + color=(0.75, 0.7, 0.8), + ) if first_selectable is None: first_selectable = btn if ba.app.ui.use_toolbars: - ba.widget(edit=btn, - right_widget=ba.internal.get_special_widget( - 'party_button')) + ba.widget( + edit=btn, + right_widget=ba.internal.get_special_widget('party_button'), + ) ba.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=50) self._unlink_accounts_button: ba.Widget | None @@ -823,7 +959,8 @@ class AccountSettingsWindow(ba.Window): size=(button_width, 60), label='', color=(0.55, 0.5, 0.6), - on_activate_call=self._unlink_accounts_press) + on_activate_call=self._unlink_accounts_press, + ) self._unlink_accounts_button_label = ba.textwidget( parent=self._subcontainer, draw_controller=btn, @@ -833,13 +970,15 @@ class AccountSettingsWindow(ba.Window): position=(self._sub_width * 0.5, v + 55), text=ba.Lstr(resource=self._r + '.unlinkAccountsText'), maxwidth=button_width * 0.8, - color=(0.75, 0.7, 0.8)) + color=(0.75, 0.7, 0.8), + ) if first_selectable is None: first_selectable = btn if ba.app.ui.use_toolbars: - ba.widget(edit=btn, - right_widget=ba.internal.get_special_widget( - 'party_button')) + ba.widget( + edit=btn, + right_widget=ba.internal.get_special_widget('party_button'), + ) ba.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=50) self._update_unlink_accounts_button() else: @@ -855,13 +994,15 @@ class AccountSettingsWindow(ba.Window): color=(0.55, 0.5, 0.6), textcolor=(0.75, 0.7, 0.8), autoselect=True, - on_activate_call=self._sign_out_press) + on_activate_call=self._sign_out_press, + ) if first_selectable is None: first_selectable = btn if ba.app.ui.use_toolbars: - ba.widget(edit=btn, - right_widget=ba.internal.get_special_widget( - 'party_button')) + ba.widget( + edit=btn, + right_widget=ba.internal.get_special_widget('party_button'), + ) ba.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=15) if show_cancel_v2_sign_in_button: @@ -874,49 +1015,63 @@ class AccountSettingsWindow(ba.Window): color=(0.55, 0.5, 0.6), textcolor=(0.75, 0.7, 0.8), autoselect=True, - on_activate_call=self._cancel_v2_sign_in_press) + on_activate_call=self._cancel_v2_sign_in_press, + ) if first_selectable is None: first_selectable = btn if ba.app.ui.use_toolbars: - ba.widget(edit=btn, - right_widget=ba.internal.get_special_widget( - 'party_button')) + ba.widget( + edit=btn, + right_widget=ba.internal.get_special_widget('party_button'), + ) ba.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=15) # Whatever the topmost selectable thing is, we want it to scroll all # the way up when we select it. if first_selectable is not None: - ba.widget(edit=first_selectable, - up_widget=bbtn, - show_buffer_top=400) + ba.widget( + edit=first_selectable, up_widget=bbtn, show_buffer_top=400 + ) # (this should re-scroll us to the top..) - ba.containerwidget(edit=self._subcontainer, - visible_child=first_selectable) + ba.containerwidget( + edit=self._subcontainer, visible_child=first_selectable + ) self._needs_refresh = False def _on_achievements_press(self) -> None: # pylint: disable=cyclic-import from bastd.ui import achievements + account_state = ba.internal.get_v1_account_state() - account_type = (ba.internal.get_v1_account_type() - if account_state == 'signed_in' else 'unknown') + account_type = ( + ba.internal.get_v1_account_type() + if account_state == 'signed_in' + else 'unknown' + ) # for google play we use the built-in UI; otherwise pop up our own if account_type == 'Google Play': - ba.timer(0.15, - ba.Call(ba.internal.show_online_score_ui, 'achievements'), - timetype=ba.TimeType.REAL) + ba.timer( + 0.15, + ba.Call(ba.internal.show_online_score_ui, 'achievements'), + timetype=ba.TimeType.REAL, + ) elif account_type != 'unknown': assert self._achievements_button is not None achievements.AchievementsWindow( - position=self._achievements_button.get_screen_space_center()) + position=self._achievements_button.get_screen_space_center() + ) else: - print('ERROR: unknown account type in on_achievements_press:', - account_type) + print( + 'ERROR: unknown account type in on_achievements_press:', + account_type, + ) def _on_leaderboards_press(self) -> None: - ba.timer(0.15, - ba.Call(ba.internal.show_online_score_ui, 'leaderboards'), - timetype=ba.TimeType.REAL) + ba.timer( + 0.15, + ba.Call(ba.internal.show_online_score_ui, 'leaderboards'), + timetype=ba.TimeType.REAL, + ) def _have_unlinkable_accounts(self) -> bool: # if this is not present, we haven't had contact from the server so @@ -924,7 +1079,8 @@ class AccountSettingsWindow(ba.Window): if ba.internal.get_public_login_id() is None: return False accounts = ba.internal.get_v1_account_misc_read_val_2( - 'linkedAccounts', []) + 'linkedAccounts', [] + ) return len(accounts) > 1 def _update_unlink_accounts_button(self) -> None: @@ -947,7 +1103,8 @@ class AccountSettingsWindow(ba.Window): accounts_str = num * '.' + (4 - num) * ' ' else: accounts = ba.internal.get_v1_account_misc_read_val_2( - 'linkedAccounts', []) + 'linkedAccounts', [] + ) # our_account = _bs.get_v1_account_display_string() # accounts = [a for a in accounts if a != our_account] # accounts_str = u', '.join(accounts) if accounts else @@ -956,15 +1113,20 @@ class AccountSettingsWindow(ba.Window): # accounts # (they can see that in the unlink section if they're curious) accounts_str = str(max(0, len(accounts) - 1)) - ba.textwidget(edit=self._linked_accounts_text, - text=ba.Lstr(value='${L} ${A}', - subs=[('${L}', - ba.Lstr(resource=self._r + - '.linkedAccountsText')), - ('${A}', accounts_str)])) + ba.textwidget( + edit=self._linked_accounts_text, + text=ba.Lstr( + value='${L} ${A}', + subs=[ + ('${L}', ba.Lstr(resource=self._r + '.linkedAccountsText')), + ('${A}', accounts_str), + ], + ), + ) def _refresh_campaign_progress_text(self) -> None: from ba.internal import getcampaign + if self._campaign_progress_text is None: return p_str: str | ba.Lstr @@ -975,9 +1137,10 @@ class AccountSettingsWindow(ba.Window): # Last level cant be completed; hence the -1; progress = min(1.0, float(levels_complete) / (len(levels) - 1)) - p_str = ba.Lstr(resource=self._r + '.campaignProgressText', - subs=[('${PROGRESS}', - str(int(progress * 100.0)) + '%')]) + p_str = ba.Lstr( + resource=self._r + '.campaignProgressText', + subs=[('${PROGRESS}', str(int(progress * 100.0)) + '%')], + ) except Exception: p_str = '?' ba.print_exception('Error calculating co-op campaign progress.') @@ -991,9 +1154,12 @@ class AccountSettingsWindow(ba.Window): except Exception: ba.print_exception() tc_str = '-' - ba.textwidget(edit=self._tickets_text, - text=ba.Lstr(resource=self._r + '.ticketsText', - subs=[('${COUNT}', tc_str)])) + ba.textwidget( + edit=self._tickets_text, + text=ba.Lstr( + resource=self._r + '.ticketsText', subs=[('${COUNT}', tc_str)] + ), + ) def _refresh_account_name_text(self) -> None: if self._account_name_text is None: @@ -1006,14 +1172,17 @@ class AccountSettingsWindow(ba.Window): ba.textwidget(edit=self._account_name_text, text=name_str) def _refresh_achievements(self) -> None: - if (self._achievements_text is None - and self._achievements_button is None): + if ( + self._achievements_text is None + and self._achievements_button is None + ): return complete = sum(1 if a.complete else 0 for a in ba.app.ach.achievements) total = len(ba.app.ach.achievements) - txt_final = ba.Lstr(resource=self._r + '.achievementProgressText', - subs=[('${COUNT}', str(complete)), - ('${TOTAL}', str(total))]) + txt_final = ba.Lstr( + resource=self._r + '.achievementProgressText', + subs=[('${COUNT}', str(complete)), ('${TOTAL}', str(total))], + ) if self._achievements_text is not None: ba.textwidget(edit=self._achievements_text, text=txt_final) @@ -1023,11 +1192,13 @@ class AccountSettingsWindow(ba.Window): def _link_accounts_press(self) -> None: # pylint: disable=cyclic-import from bastd.ui.account import link + link.AccountLinkWindow(origin_widget=self._link_accounts_button) def _unlink_accounts_press(self) -> None: # pylint: disable=cyclic-import from bastd.ui.account import unlink + if not self._have_unlinkable_accounts(): ba.playsound(ba.getsound('error')) return @@ -1036,10 +1207,12 @@ class AccountSettingsWindow(ba.Window): def _player_profiles_press(self) -> None: # pylint: disable=cyclic-import from bastd.ui.profile import browser as pbrowser + self._save_state() ba.containerwidget(edit=self._root_widget, transition='out_left') pbrowser.ProfileBrowserWindow( - origin_widget=self._player_profiles_button) + origin_widget=self._player_profiles_button + ) def _cancel_v2_sign_in_press(self) -> None: # Just say we don't wanna be signed in anymore. @@ -1061,15 +1234,17 @@ class AccountSettingsWindow(ba.Window): # signed in at this point (affects v1 accounts). cfg['Auto Account State'] = 'signed_out' cfg.commit() - ba.buttonwidget(edit=self._sign_out_button, - label=ba.Lstr(resource=self._r + '.signingOutText')) + ba.buttonwidget( + edit=self._sign_out_button, + label=ba.Lstr(resource=self._r + '.signingOutText'), + ) # Speed UI updates along. ba.timer(0.1, ba.WeakCall(self._update), timetype=ba.TimeType.REAL) - def _sign_in_press(self, - account_type: str, - show_test_warning: bool = True) -> None: + def _sign_in_press( + self, account_type: str, show_test_warning: bool = True + ) -> None: del show_test_warning # unused ba.internal.sign_in_v1(account_type) @@ -1083,12 +1258,14 @@ class AccountSettingsWindow(ba.Window): def _v2_sign_in_press(self) -> None: # pylint: disable=cyclic-import from bastd.ui.account.v2 import V2SignInWindow + assert self._sign_in_v2_button is not None V2SignInWindow(origin_widget=self._sign_in_v2_button) def _reset_progress(self) -> None: try: from ba.internal import getcampaign + # FIXME: This would need to happen server-side these days. if self._can_reset_achievements: ba.app.config['Achievements'] = {} @@ -1106,13 +1283,16 @@ class AccountSettingsWindow(ba.Window): def _back(self) -> None: # pylint: disable=cyclic-import from bastd.ui.mainmenu import MainMenuWindow + self._save_state() - ba.containerwidget(edit=self._root_widget, - transition=self._transition_out) + ba.containerwidget( + edit=self._root_widget, transition=self._transition_out + ) if not self._modal: ba.app.ui.set_main_menu_window( - MainMenuWindow(transition='in_left').get_root_widget()) + MainMenuWindow(transition='in_left').get_root_widget() + ) def _save_state(self) -> None: try: diff --git a/dist/ba_data/python/bastd/ui/account/unlink.py b/dist/ba_data/python/bastd/ui/account/unlink.py index 84dffb9..ffbce7d 100644 --- a/dist/ba_data/python/bastd/ui/account/unlink.py +++ b/dist/ba_data/python/bastd/ui/account/unlink.py @@ -33,24 +33,36 @@ class AccountUnlinkWindow(ba.Window): self._scroll_width = 400 self._scroll_height = 200 uiscale = ba.app.ui.uiscale - base_scale = (2.0 if uiscale is ba.UIScale.SMALL else - 1.6 if uiscale is ba.UIScale.MEDIUM else 1.1) - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height), - transition=transition, - scale=base_scale, - scale_origin_stack_offset=scale_origin, - stack_offset=(0, -10) if uiscale is ba.UIScale.SMALL else (0, 0))) - self._cancel_button = ba.buttonwidget(parent=self._root_widget, - position=(30, self._height - 50), - size=(50, 50), - scale=0.7, - label='', - color=bg_color, - on_activate_call=self._cancel, - autoselect=True, - icon=ba.gettexture('crossOut'), - iconscale=1.2) + base_scale = ( + 2.0 + if uiscale is ba.UIScale.SMALL + else 1.6 + if uiscale is ba.UIScale.MEDIUM + else 1.1 + ) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height), + transition=transition, + scale=base_scale, + scale_origin_stack_offset=scale_origin, + stack_offset=(0, -10) + if uiscale is ba.UIScale.SMALL + else (0, 0), + ) + ) + self._cancel_button = ba.buttonwidget( + parent=self._root_widget, + position=(30, self._height - 50), + size=(50, 50), + scale=0.7, + label='', + color=bg_color, + on_activate_call=self._cancel, + autoselect=True, + icon=ba.gettexture('crossOut'), + iconscale=1.2, + ) ba.textwidget( parent=self._root_widget, position=(self._width * 0.5, self._height * 0.88), @@ -61,62 +73,76 @@ class AccountUnlinkWindow(ba.Window): maxwidth=self._width * 0.7, color=ba.app.ui.infotextcolor, h_align='center', - v_align='center') - ba.containerwidget(edit=self._root_widget, - cancel_button=self._cancel_button) + v_align='center', + ) + ba.containerwidget( + edit=self._root_widget, cancel_button=self._cancel_button + ) self._scrollwidget = ba.scrollwidget( parent=self._root_widget, highlight=False, - position=((self._width - self._scroll_width) * 0.5, - self._height - 85 - self._scroll_height), - size=(self._scroll_width, self._scroll_height)) + position=( + (self._width - self._scroll_width) * 0.5, + self._height - 85 - self._scroll_height, + ), + size=(self._scroll_width, self._scroll_height), + ) ba.containerwidget(edit=self._scrollwidget, claims_left_right=True) - self._columnwidget = ba.columnwidget(parent=self._scrollwidget, - border=2, - margin=0, - left_border=10) + self._columnwidget = ba.columnwidget( + parent=self._scrollwidget, border=2, margin=0, left_border=10 + ) our_login_id = ba.internal.get_public_login_id() if our_login_id is None: entries = [] else: account_infos = ba.internal.get_v1_account_misc_read_val_2( - 'linkedAccounts2', []) - entries = [{ - 'name': ai['d'], - 'id': ai['id'] - } for ai in account_infos if ai['id'] != our_login_id] + 'linkedAccounts2', [] + ) + entries = [ + {'name': ai['d'], 'id': ai['id']} + for ai in account_infos + if ai['id'] != our_login_id + ] # (avoid getting our selection stuck on an empty column widget) if not entries: ba.containerwidget(edit=self._scrollwidget, selectable=False) for i, entry in enumerate(entries): - txt = ba.textwidget(parent=self._columnwidget, - selectable=True, - text=entry['name'], - size=(self._scroll_width - 30, 30), - autoselect=True, - click_activate=True, - on_activate_call=ba.Call( - self._on_entry_selected, entry)) + txt = ba.textwidget( + parent=self._columnwidget, + selectable=True, + text=entry['name'], + size=(self._scroll_width - 30, 30), + autoselect=True, + click_activate=True, + on_activate_call=ba.Call(self._on_entry_selected, entry), + ) ba.widget(edit=txt, left_widget=self._cancel_button) if i == 0: ba.widget(edit=txt, up_widget=self._cancel_button) def _on_entry_selected(self, entry: dict[str, Any]) -> None: - ba.screenmessage(ba.Lstr(resource='pleaseWaitText', - fallback_resource='requestingText'), - color=(0, 1, 0)) - ba.internal.add_transaction({ - 'type': 'ACCOUNT_UNLINK_REQUEST', - 'accountID': entry['id'], - 'expire_time': time.time() + 5 - }) + ba.screenmessage( + ba.Lstr( + resource='pleaseWaitText', fallback_resource='requestingText' + ), + color=(0, 1, 0), + ) + ba.internal.add_transaction( + { + 'type': 'ACCOUNT_UNLINK_REQUEST', + 'accountID': entry['id'], + 'expire_time': time.time() + 5, + } + ) ba.internal.run_transactions() - ba.containerwidget(edit=self._root_widget, - transition=self._transition_out) + ba.containerwidget( + edit=self._root_widget, transition=self._transition_out + ) def _cancel(self) -> None: - ba.containerwidget(edit=self._root_widget, - transition=self._transition_out) + ba.containerwidget( + edit=self._root_widget, transition=self._transition_out + ) diff --git a/dist/ba_data/python/bastd/ui/account/v2.py b/dist/ba_data/python/bastd/ui/account/v2.py index 38047c2..7f155e8 100644 --- a/dist/ba_data/python/bastd/ui/account/v2.py +++ b/dist/ba_data/python/bastd/ui/account/v2.py @@ -29,12 +29,22 @@ class V2SignInWindow(ba.Window): self._proxykey: str | None = None uiscale = ba.app.ui.uiscale - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height), - transition='in_scale', - scale_origin_stack_offset=origin_widget.get_screen_space_center(), - scale=(1.25 if uiscale is ba.UIScale.SMALL else - 1.05 if uiscale is ba.UIScale.MEDIUM else 0.9))) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height), + transition='in_scale', + scale_origin_stack_offset=( + origin_widget.get_screen_space_center() + ), + scale=( + 1.25 + if uiscale is ba.UIScale.SMALL + else 1.05 + if uiscale is ba.UIScale.MEDIUM + else 0.9 + ), + ) + ) self._loading_text = ba.textwidget( parent=self._root_widget, @@ -43,8 +53,10 @@ class V2SignInWindow(ba.Window): v_align='center', size=(0, 0), maxwidth=0.9 * self._width, - text=ba.Lstr(value='${A}...', - subs=[('${A}', ba.Lstr(resource='loadingText'))]), + text=ba.Lstr( + value='${A}...', + subs=[('${A}', ba.Lstr(resource='loadingText'))], + ), ) self._cancel_button = ba.buttonwidget( @@ -57,15 +69,17 @@ class V2SignInWindow(ba.Window): autoselect=True, textcolor=(0.75, 0.7, 0.8), ) - ba.containerwidget(edit=self._root_widget, - cancel_button=self._cancel_button) + ba.containerwidget( + edit=self._root_widget, cancel_button=self._cancel_button + ) self._update_timer: ba.Timer | None = None # Ask the cloud for a proxy login id. - ba.app.cloud.send_message_cb(bacommon.cloud.LoginProxyRequestMessage(), - on_response=ba.WeakCall( - self._on_proxy_request_response)) + ba.app.cloud.send_message_cb( + bacommon.cloud.LoginProxyRequestMessage(), + on_response=ba.WeakCall(self._on_proxy_request_response), + ) def _on_proxy_request_response( self, response: bacommon.cloud.LoginProxyRequestResponse | Exception @@ -77,12 +91,14 @@ class V2SignInWindow(ba.Window): ba.textwidget( edit=self._loading_text, text=ba.Lstr(resource='internal.unavailableNoConnectionText'), - color=(1, 0, 0)) + color=(1, 0, 0), + ) return # Show link(s) the user can use to log in. - address = ba.internal.get_master_server_address( - version=2) + response.url + address = ( + ba.internal.get_master_server_address(version=2) + response.url + ) address_pretty = address.removeprefix('https://') ba.textwidget( @@ -90,55 +106,70 @@ class V2SignInWindow(ba.Window): position=(self._width * 0.5, self._height - 95), size=(0, 0), text=ba.Lstr( - resource='accountSettingsWindow.v2LinkInstructionsText'), + resource='accountSettingsWindow.v2LinkInstructionsText' + ), color=ba.app.ui.title_color, maxwidth=self._width * 0.9, h_align='center', - v_align='center') + v_align='center', + ) button_width = 450 if is_browser_likely_available(): - ba.buttonwidget(parent=self._root_widget, - position=((self._width * 0.5 - button_width * 0.5), - self._height - 185), - autoselect=True, - size=(button_width, 60), - label=ba.Lstr(value=address_pretty), - color=(0.55, 0.5, 0.6), - textcolor=(0.75, 0.7, 0.8), - on_activate_call=lambda: ba.open_url(address)) + ba.buttonwidget( + parent=self._root_widget, + position=( + (self._width * 0.5 - button_width * 0.5), + self._height - 185, + ), + autoselect=True, + size=(button_width, 60), + label=ba.Lstr(value=address_pretty), + color=(0.55, 0.5, 0.6), + textcolor=(0.75, 0.7, 0.8), + on_activate_call=lambda: ba.open_url(address), + ) qroffs = 0.0 else: - ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, self._height - 145), - size=(0, 0), - text=ba.Lstr(value=address_pretty), - flatness=1.0, - maxwidth=self._width, - scale=0.75, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, self._height - 145), + size=(0, 0), + text=ba.Lstr(value=address_pretty), + flatness=1.0, + maxwidth=self._width, + scale=0.75, + h_align='center', + v_align='center', + ) qroffs = 20.0 qr_size = 270 - ba.imagewidget(parent=self._root_widget, - position=(self._width * 0.5 - qr_size * 0.5, - self._height * 0.36 + qroffs - qr_size * 0.5), - size=(qr_size, qr_size), - texture=ba.internal.get_qrcode_texture(address)) + ba.imagewidget( + parent=self._root_widget, + position=( + self._width * 0.5 - qr_size * 0.5, + self._height * 0.36 + qroffs - qr_size * 0.5, + ), + size=(qr_size, qr_size), + texture=ba.internal.get_qrcode_texture(address), + ) # Start querying for results. self._proxyid = response.proxyid self._proxykey = response.proxykey - ba.timer(STATUS_CHECK_INTERVAL_SECONDS, - ba.WeakCall(self._ask_for_status)) + ba.timer( + STATUS_CHECK_INTERVAL_SECONDS, ba.WeakCall(self._ask_for_status) + ) def _ask_for_status(self) -> None: assert self._proxyid is not None assert self._proxykey is not None ba.app.cloud.send_message_cb( bacommon.cloud.LoginProxyStateQueryMessage( - proxyid=self._proxyid, proxykey=self._proxykey), - on_response=ba.WeakCall(self._got_status)) + proxyid=self._proxyid, proxykey=self._proxykey + ), + on_response=ba.WeakCall(self._got_status), + ) def _got_status( self, response: bacommon.cloud.LoginProxyStateQueryResponse | Exception @@ -146,16 +177,20 @@ class V2SignInWindow(ba.Window): # For now, if anything goes wrong on the server-side, just abort # with a vague error message. Can be more verbose later if need be. - if (isinstance(response, bacommon.cloud.LoginProxyStateQueryResponse) - and response.state is response.State.FAIL): + if ( + isinstance(response, bacommon.cloud.LoginProxyStateQueryResponse) + and response.state is response.State.FAIL + ): ba.playsound(ba.getsound('error')) ba.screenmessage(ba.Lstr(resource='errorText'), color=(1, 0, 0)) self._done() return # If we got a token, set ourself as signed in. Hooray! - if (isinstance(response, bacommon.cloud.LoginProxyStateQueryResponse) - and response.state is response.State.SUCCESS): + if ( + isinstance(response, bacommon.cloud.LoginProxyStateQueryResponse) + and response.state is response.State.SUCCESS + ): assert response.credentials is not None ba.app.accounts_v2.set_primary_credentials(response.credentials) @@ -165,23 +200,29 @@ class V2SignInWindow(ba.Window): try: ba.app.cloud.send_message_cb( bacommon.cloud.LoginProxyCompleteMessage( - proxyid=self._proxyid), - on_response=ba.WeakCall(self._proxy_complete_response)) + proxyid=self._proxyid + ), + on_response=ba.WeakCall(self._proxy_complete_response), + ) except CommunicationError: pass except Exception: logging.warning( 'Unexpected error sending login-proxy-complete message', - exc_info=True) + exc_info=True, + ) self._done() return # If we're still waiting, ask again soon. - if (isinstance(response, Exception) - or response.state is response.State.WAITING): - ba.timer(STATUS_CHECK_INTERVAL_SECONDS, - ba.WeakCall(self._ask_for_status)) + if ( + isinstance(response, Exception) + or response.state is response.State.WAITING + ): + ba.timer( + STATUS_CHECK_INTERVAL_SECONDS, ba.WeakCall(self._ask_for_status) + ) def _proxy_complete_response(self, response: None | Exception) -> None: del response # Not used. diff --git a/dist/ba_data/python/bastd/ui/account/viewer.py b/dist/ba_data/python/bastd/ui/account/viewer.py index d62848f..d8cdfbc 100644 --- a/dist/ba_data/python/bastd/ui/account/viewer.py +++ b/dist/ba_data/python/bastd/ui/account/viewer.py @@ -17,12 +17,14 @@ if TYPE_CHECKING: class AccountViewerWindow(popup.PopupWindow): """Popup window that displays info for an account.""" - def __init__(self, - account_id: str, - profile_id: str | None = None, - position: tuple[float, float] = (0.0, 0.0), - scale: float | None = None, - offset: tuple[float, float] = (0.0, 0.0)): + def __init__( + self, + account_id: str, + profile_id: str | None = None, + position: tuple[float, float] = (0.0, 0.0), + scale: float | None = None, + offset: tuple[float, float] = (0.0, 0.0), + ): from ba.internal import is_browser_likely_available, master_server_get self._account_id = account_id @@ -30,24 +32,36 @@ class AccountViewerWindow(popup.PopupWindow): uiscale = ba.app.ui.uiscale if scale is None: - scale = (2.6 if uiscale is ba.UIScale.SMALL else - 1.8 if uiscale is ba.UIScale.MEDIUM else 1.4) + scale = ( + 2.6 + if uiscale is ba.UIScale.SMALL + else 1.8 + if uiscale is ba.UIScale.MEDIUM + else 1.4 + ) self._transitioning_out = False self._width = 400 - self._height = (300 if uiscale is ba.UIScale.SMALL else - 400 if uiscale is ba.UIScale.MEDIUM else 450) + self._height = ( + 300 + if uiscale is ba.UIScale.SMALL + else 400 + if uiscale is ba.UIScale.MEDIUM + else 450 + ) self._subcontainer: ba.Widget | None = None bg_color = (0.5, 0.4, 0.6) # Creates our _root_widget. - popup.PopupWindow.__init__(self, - position=position, - size=(self._width, self._height), - scale=scale, - bg_color=bg_color, - offset=offset) + popup.PopupWindow.__init__( + self, + position=position, + size=(self._width, self._height), + scale=scale, + bg_color=bg_color, + offset=offset, + ) self._cancel_button = ba.buttonwidget( parent=self.root_widget, @@ -59,7 +73,8 @@ class AccountViewerWindow(popup.PopupWindow): on_activate_call=self._on_cancel_press, autoselect=True, icon=ba.gettexture('crossOut'), - iconscale=1.2) + iconscale=1.2, + ) self._title_text = ba.textwidget( parent=self.root_widget, @@ -70,30 +85,38 @@ class AccountViewerWindow(popup.PopupWindow): scale=0.6, text=ba.Lstr(resource='playerInfoText'), maxwidth=200, - color=(0.7, 0.7, 0.7, 0.7)) + color=(0.7, 0.7, 0.7, 0.7), + ) - self._scrollwidget = ba.scrollwidget(parent=self.root_widget, - size=(self._width - 60, - self._height - 70), - position=(30, 30), - capture_arrows=True, - simple_culling_v=10) + self._scrollwidget = ba.scrollwidget( + parent=self.root_widget, + size=(self._width - 60, self._height - 70), + position=(30, 30), + capture_arrows=True, + simple_culling_v=10, + ) ba.widget(edit=self._scrollwidget, autoselect=True) self._loading_text = ba.textwidget( parent=self._scrollwidget, scale=0.5, - text=ba.Lstr(value='${A}...', - subs=[('${A}', ba.Lstr(resource='loadingText'))]), + text=ba.Lstr( + value='${A}...', + subs=[('${A}', ba.Lstr(resource='loadingText'))], + ), size=(self._width - 60, 100), h_align='center', - v_align='center') + v_align='center', + ) # In cases where the user most likely has a browser/email, lets # offer a 'report this user' button. - if (is_browser_likely_available() - and ba.internal.get_v1_account_misc_read_val( - 'showAccountExtrasMenu', False)): + if ( + is_browser_likely_available() + and ba.internal.get_v1_account_misc_read_val( + 'showAccountExtrasMenu', False + ) + ): self._extras_menu_button = ba.buttonwidget( parent=self.root_widget, @@ -104,20 +127,26 @@ class AccountViewerWindow(popup.PopupWindow): button_type='square', color=(0.64, 0.52, 0.69), textcolor=(0.57, 0.47, 0.57), - on_activate_call=self._on_extras_menu_press) + on_activate_call=self._on_extras_menu_press, + ) - ba.containerwidget(edit=self.root_widget, - cancel_button=self._cancel_button) + ba.containerwidget( + edit=self.root_widget, cancel_button=self._cancel_button + ) - master_server_get('bsAccountInfo', { - 'buildNumber': ba.app.build_number, - 'accountID': self._account_id, - 'profileID': self._profile_id - }, - callback=ba.WeakCall(self._on_query_response)) + master_server_get( + 'bsAccountInfo', + { + 'buildNumber': ba.app.build_number, + 'accountID': self._account_id, + 'profileID': self._profile_id, + }, + callback=ba.WeakCall(self._on_query_response), + ) - def popup_menu_selected_choice(self, window: popup.PopupMenu, - choice: str) -> None: + def popup_menu_selected_choice( + self, window: popup.PopupMenu, choice: str + ) -> None: """Called when a menu entry is selected.""" del window # Unused arg. if choice == 'more': @@ -136,7 +165,7 @@ class AccountViewerWindow(popup.PopupWindow): choices = ['more', 'report'] choices_display = [ ba.Lstr(resource='coopSelectWindow.seeMoreText'), - ba.Lstr(resource='reportThisPlayerText') + ba.Lstr(resource='reportThisPlayerText'), ] is_admin = False if is_admin: @@ -147,28 +176,38 @@ class AccountViewerWindow(popup.PopupWindow): uiscale = ba.app.ui.uiscale popup.PopupMenuWindow( position=self._extras_menu_button.get_screen_space_center(), - scale=(2.3 if uiscale is ba.UIScale.SMALL else - 1.65 if uiscale is ba.UIScale.MEDIUM else 1.23), + scale=( + 2.3 + if uiscale is ba.UIScale.SMALL + else 1.65 + if uiscale is ba.UIScale.MEDIUM + else 1.23 + ), choices=choices, choices_display=choices_display, current_choice='more', - delegate=self) + delegate=self, + ) def _on_ban_press(self) -> None: - ba.internal.add_transaction({ - 'type': 'BAN_ACCOUNT', - 'account': self._account_id - }) + ba.internal.add_transaction( + {'type': 'BAN_ACCOUNT', 'account': self._account_id} + ) ba.internal.run_transactions() def _on_report_press(self) -> None: from bastd.ui import report - report.ReportPlayerWindow(self._account_id, - origin_widget=self._extras_menu_button) + + report.ReportPlayerWindow( + self._account_id, origin_widget=self._extras_menu_button + ) def _on_more_press(self) -> None: - ba.open_url(ba.internal.get_master_server_address() + - '/highscores?profile=' + self._account_id) + ba.open_url( + ba.internal.get_master_server_address() + + '/highscores?profile=' + + self._account_id + ) def _on_query_response(self, data: dict[str, Any] | None) -> None: # FIXME: Tidy this up. @@ -179,7 +218,8 @@ class AccountViewerWindow(popup.PopupWindow): if data is None: ba.textwidget( edit=self._loading_text, - text=ba.Lstr(resource='internal.unavailableNoConnectionText')) + text=ba.Lstr(resource='internal.unavailableNoConnectionText'), + ) else: try: self._loading_text.delete() @@ -188,10 +228,10 @@ class AccountViewerWindow(popup.PopupWindow): trophystr = data['trophies'] num = 10 chunks = [ - trophystr[i:i + num] + trophystr[i : i + num] for i in range(0, len(trophystr), num) ] - trophystr = ('\n\n'.join(chunks)) + trophystr = '\n\n'.join(chunks) if trophystr == '': trophystr = '-' except Exception: @@ -199,14 +239,19 @@ class AccountViewerWindow(popup.PopupWindow): account_name_spacing = 15 tscale = 0.65 ts_height = ba.internal.get_string_height( - trophystr, suppress_warning=True) + trophystr, suppress_warning=True + ) sub_width = self._width - 80 - sub_height = 200 + ts_height * tscale + \ - account_name_spacing * len(data['accountDisplayStrings']) + sub_height = ( + 200 + + ts_height * tscale + + account_name_spacing * len(data['accountDisplayStrings']) + ) self._subcontainer = ba.containerwidget( parent=self._scrollwidget, size=(sub_width, sub_height), - background=False) + background=False, + ) v = sub_height - 20 title_scale = 0.37 @@ -219,17 +264,24 @@ class AccountViewerWindow(popup.PopupWindow): if data['profile'] is not None: profile = data['profile'] character = ba.app.spaz_appearances.get( - profile['character'], None) + profile['character'], None + ) if character is not None: - tint_color = (profile['color'] if 'color' - in profile else (1, 1, 1)) - tint2_color = (profile['highlight'] - if 'highlight' in profile else - (1, 1, 1)) + tint_color = ( + profile['color'] + if 'color' in profile + else (1, 1, 1) + ) + tint2_color = ( + profile['highlight'] + if 'highlight' in profile + else (1, 1, 1) + ) icon_tex = character.icon_texture tint_tex = character.icon_mask_texture mask_texture = ba.gettexture( - 'characterIconMask') + 'characterIconMask' + ) ba.imagewidget( parent=self._subcontainer, position=(sub_width * center - 40, v - 80), @@ -239,7 +291,8 @@ class AccountViewerWindow(popup.PopupWindow): texture=ba.gettexture(icon_tex), tint_texture=ba.gettexture(tint_tex), tint_color=tint_color, - tint2_color=tint2_color) + tint2_color=tint2_color, + ) v -= 95 except Exception: ba.print_exception('Error displaying character.') @@ -253,7 +306,8 @@ class AccountViewerWindow(popup.PopupWindow): color=ba.safecolor(tint_color, 0.7), shadow=1.0, text=ba.Lstr(value=data['profileDisplayString']), - maxwidth=sub_width * maxwidth_scale * 0.75) + maxwidth=sub_width * maxwidth_scale * 0.75, + ) showing_character = True v -= 33 @@ -263,172 +317,228 @@ class AccountViewerWindow(popup.PopupWindow): v = sub_height - 20 if len(data['accountDisplayStrings']) <= 1: account_title = ba.Lstr( - resource='settingsWindow.accountText') + resource='settingsWindow.accountText' + ) else: account_title = ba.Lstr( resource='accountSettingsWindow.accountsText', - fallback_resource='settingsWindow.accountText') - ba.textwidget(parent=self._subcontainer, - size=(0, 0), - position=(sub_width * center, v), - flatness=1.0, - h_align='center', - v_align='center', - scale=title_scale, - color=ba.app.ui.infotextcolor, - text=account_title, - maxwidth=sub_width * maxwidth_scale) - draw_small = (showing_character - or len(data['accountDisplayStrings']) > 1) + fallback_resource='settingsWindow.accountText', + ) + ba.textwidget( + parent=self._subcontainer, + size=(0, 0), + position=(sub_width * center, v), + flatness=1.0, + h_align='center', + v_align='center', + scale=title_scale, + color=ba.app.ui.infotextcolor, + text=account_title, + maxwidth=sub_width * maxwidth_scale, + ) + draw_small = ( + showing_character or len(data['accountDisplayStrings']) > 1 + ) v -= 14 if draw_small else 20 for account_string in data['accountDisplayStrings']: - ba.textwidget(parent=self._subcontainer, - size=(0, 0), - position=(sub_width * center, v), - h_align='center', - v_align='center', - scale=0.55 if draw_small else 0.8, - text=account_string, - maxwidth=sub_width * maxwidth_scale) + ba.textwidget( + parent=self._subcontainer, + size=(0, 0), + position=(sub_width * center, v), + h_align='center', + v_align='center', + scale=0.55 if draw_small else 0.8, + text=account_string, + maxwidth=sub_width * maxwidth_scale, + ) v -= account_name_spacing v += account_name_spacing v -= 25 if showing_character else 29 - ba.textwidget(parent=self._subcontainer, - size=(0, 0), - position=(sub_width * center, v), - flatness=1.0, - h_align='center', - v_align='center', - scale=title_scale, - color=ba.app.ui.infotextcolor, - text=ba.Lstr(resource='rankText'), - maxwidth=sub_width * maxwidth_scale) + ba.textwidget( + parent=self._subcontainer, + size=(0, 0), + position=(sub_width * center, v), + flatness=1.0, + h_align='center', + v_align='center', + scale=title_scale, + color=ba.app.ui.infotextcolor, + text=ba.Lstr(resource='rankText'), + maxwidth=sub_width * maxwidth_scale, + ) v -= 14 if data['rank'] is None: rank_str = '-' suffix_offset = None else: str_raw = ba.Lstr( - resource='league.rankInLeagueText').evaluate() + resource='league.rankInLeagueText' + ).evaluate() # FIXME: Would be nice to not have to eval this. rank_str = ba.Lstr( resource='league.rankInLeagueText', - subs=[('${RANK}', str(data['rank'][2])), - ('${NAME}', - ba.Lstr(translate=('leagueNames', - data['rank'][0]))), - ('${SUFFIX}', '')]).evaluate() + subs=[ + ('${RANK}', str(data['rank'][2])), + ( + '${NAME}', + ba.Lstr( + translate=('leagueNames', data['rank'][0]) + ), + ), + ('${SUFFIX}', ''), + ], + ).evaluate() rank_str_width = min( sub_width * maxwidth_scale, ba.internal.get_string_width( - rank_str, suppress_warning=True) * 0.55) + rank_str, suppress_warning=True + ) + * 0.55, + ) # Only tack our suffix on if its at the end and only for # non-diamond leagues. - if (str_raw.endswith('${SUFFIX}') - and data['rank'][0] != 'Diamond'): + if ( + str_raw.endswith('${SUFFIX}') + and data['rank'][0] != 'Diamond' + ): suffix_offset = rank_str_width * 0.5 + 2 else: suffix_offset = None - ba.textwidget(parent=self._subcontainer, - size=(0, 0), - position=(sub_width * center, v), - h_align='center', - v_align='center', - scale=0.55, - text=rank_str, - maxwidth=sub_width * maxwidth_scale) + ba.textwidget( + parent=self._subcontainer, + size=(0, 0), + position=(sub_width * center, v), + h_align='center', + v_align='center', + scale=0.55, + text=rank_str, + maxwidth=sub_width * maxwidth_scale, + ) if suffix_offset is not None: assert data['rank'] is not None - ba.textwidget(parent=self._subcontainer, - size=(0, 0), - position=(sub_width * center + suffix_offset, - v + 3), - h_align='left', - v_align='center', - scale=0.29, - flatness=1.0, - text='[' + str(data['rank'][1]) + ']') + ba.textwidget( + parent=self._subcontainer, + size=(0, 0), + position=(sub_width * center + suffix_offset, v + 3), + h_align='left', + v_align='center', + scale=0.29, + flatness=1.0, + text='[' + str(data['rank'][1]) + ']', + ) v -= 14 - str_raw = ba.Lstr( - resource='league.rankInLeagueText').evaluate() + str_raw = ba.Lstr(resource='league.rankInLeagueText').evaluate() old_offs = -50 prev_ranks_shown = 0 for prev_rank in data['prevRanks']: rank_str = ba.Lstr( value='${S}: ${I}', subs=[ - ('${S}', - ba.Lstr(resource='league.seasonText', - subs=[('${NUMBER}', str(prev_rank[0]))])), - ('${I}', - ba.Lstr(resource='league.rankInLeagueText', - subs=[('${RANK}', str(prev_rank[3])), - ('${NAME}', - ba.Lstr(translate=('leagueNames', - prev_rank[1]))), - ('${SUFFIX}', '')])) - ]).evaluate() + ( + '${S}', + ba.Lstr( + resource='league.seasonText', + subs=[('${NUMBER}', str(prev_rank[0]))], + ), + ), + ( + '${I}', + ba.Lstr( + resource='league.rankInLeagueText', + subs=[ + ('${RANK}', str(prev_rank[3])), + ( + '${NAME}', + ba.Lstr( + translate=( + 'leagueNames', + prev_rank[1], + ) + ), + ), + ('${SUFFIX}', ''), + ], + ), + ), + ], + ).evaluate() rank_str_width = min( sub_width * maxwidth_scale, ba.internal.get_string_width( - rank_str, suppress_warning=True) * 0.3) + rank_str, suppress_warning=True + ) + * 0.3, + ) # Only tack our suffix on if its at the end and only for # non-diamond leagues. - if (str_raw.endswith('${SUFFIX}') - and prev_rank[1] != 'Diamond'): + if ( + str_raw.endswith('${SUFFIX}') + and prev_rank[1] != 'Diamond' + ): suffix_offset = rank_str_width + 2 else: suffix_offset = None - ba.textwidget(parent=self._subcontainer, - size=(0, 0), - position=(sub_width * center + old_offs, v), - h_align='left', - v_align='center', - scale=0.3, - text=rank_str, - flatness=1.0, - maxwidth=sub_width * maxwidth_scale) + ba.textwidget( + parent=self._subcontainer, + size=(0, 0), + position=(sub_width * center + old_offs, v), + h_align='left', + v_align='center', + scale=0.3, + text=rank_str, + flatness=1.0, + maxwidth=sub_width * maxwidth_scale, + ) if suffix_offset is not None: - ba.textwidget(parent=self._subcontainer, - size=(0, 0), - position=(sub_width * center + old_offs + - suffix_offset, v + 1), - h_align='left', - v_align='center', - scale=0.20, - flatness=1.0, - text='[' + str(prev_rank[2]) + ']') + ba.textwidget( + parent=self._subcontainer, + size=(0, 0), + position=( + sub_width * center + old_offs + suffix_offset, + v + 1, + ), + h_align='left', + v_align='center', + scale=0.20, + flatness=1.0, + text='[' + str(prev_rank[2]) + ']', + ) prev_ranks_shown += 1 v -= 10 v -= 13 - ba.textwidget(parent=self._subcontainer, - size=(0, 0), - position=(sub_width * center, v), - flatness=1.0, - h_align='center', - v_align='center', - scale=title_scale, - color=ba.app.ui.infotextcolor, - text=ba.Lstr(resource='achievementsText'), - maxwidth=sub_width * maxwidth_scale) + ba.textwidget( + parent=self._subcontainer, + size=(0, 0), + position=(sub_width * center, v), + flatness=1.0, + h_align='center', + v_align='center', + scale=title_scale, + color=ba.app.ui.infotextcolor, + text=ba.Lstr(resource='achievementsText'), + maxwidth=sub_width * maxwidth_scale, + ) v -= 14 - ba.textwidget(parent=self._subcontainer, - size=(0, 0), - position=(sub_width * center, v), - h_align='center', - v_align='center', - scale=0.55, - text=str(data['achievementsCompleted']) + ' / ' + - str(len(ba.app.ach.achievements)), - maxwidth=sub_width * maxwidth_scale) + ba.textwidget( + parent=self._subcontainer, + size=(0, 0), + position=(sub_width * center, v), + h_align='center', + v_align='center', + scale=0.55, + text=str(data['achievementsCompleted']) + + ' / ' + + str(len(ba.app.ach.achievements)), + maxwidth=sub_width * maxwidth_scale, + ) v -= 25 if prev_ranks_shown == 0 and showing_character: @@ -439,26 +549,31 @@ class AccountViewerWindow(popup.PopupWindow): center = 0.5 maxwidth_scale = 0.9 - ba.textwidget(parent=self._subcontainer, - size=(0, 0), - position=(sub_width * center, v), - h_align='center', - v_align='center', - scale=title_scale, - color=ba.app.ui.infotextcolor, - flatness=1.0, - text=ba.Lstr(resource='trophiesThisSeasonText', - fallback_resource='trophiesText'), - maxwidth=sub_width * maxwidth_scale) + ba.textwidget( + parent=self._subcontainer, + size=(0, 0), + position=(sub_width * center, v), + h_align='center', + v_align='center', + scale=title_scale, + color=ba.app.ui.infotextcolor, + flatness=1.0, + text=ba.Lstr( + resource='trophiesThisSeasonText', + fallback_resource='trophiesText', + ), + maxwidth=sub_width * maxwidth_scale, + ) v -= 19 - ba.textwidget(parent=self._subcontainer, - size=(0, ts_height), - position=(sub_width * 0.5, - v - ts_height * tscale), - h_align='center', - v_align='top', - corner_scale=tscale, - text=trophystr) + ba.textwidget( + parent=self._subcontainer, + size=(0, ts_height), + position=(sub_width * 0.5, v - ts_height * tscale), + h_align='center', + v_align='top', + corner_scale=tscale, + text=trophystr, + ) except Exception: ba.print_exception('Error displaying account info.') diff --git a/dist/ba_data/python/bastd/ui/achievements.py b/dist/ba_data/python/bastd/ui/achievements.py index c5bc5d2..cc862a9 100644 --- a/dist/ba_data/python/bastd/ui/achievements.py +++ b/dist/ba_data/python/bastd/ui/achievements.py @@ -16,26 +16,38 @@ if TYPE_CHECKING: class AchievementsWindow(popup.PopupWindow): """Popup window to view achievements.""" - def __init__(self, - position: tuple[float, float], - scale: float | None = None): + def __init__( + self, position: tuple[float, float], scale: float | None = None + ): # pylint: disable=too-many-locals uiscale = ba.app.ui.uiscale if scale is None: - scale = (2.3 if uiscale is ba.UIScale.SMALL else - 1.65 if uiscale is ba.UIScale.MEDIUM else 1.23) + scale = ( + 2.3 + if uiscale is ba.UIScale.SMALL + else 1.65 + if uiscale is ba.UIScale.MEDIUM + else 1.23 + ) self._transitioning_out = False self._width = 450 - self._height = (300 if uiscale is ba.UIScale.SMALL else - 370 if uiscale is ba.UIScale.MEDIUM else 450) + self._height = ( + 300 + if uiscale is ba.UIScale.SMALL + else 370 + if uiscale is ba.UIScale.MEDIUM + else 450 + ) bg_color = (0.5, 0.4, 0.6) # creates our _root_widget - popup.PopupWindow.__init__(self, - position=position, - size=(self._width, self._height), - scale=scale, - bg_color=bg_color) + popup.PopupWindow.__init__( + self, + position=position, + size=(self._width, self._height), + scale=scale, + bg_color=bg_color, + ) self._cancel_button = ba.buttonwidget( parent=self.root_widget, @@ -47,36 +59,43 @@ class AchievementsWindow(popup.PopupWindow): on_activate_call=self._on_cancel_press, autoselect=True, icon=ba.gettexture('crossOut'), - iconscale=1.2) + iconscale=1.2, + ) achievements = ba.app.ach.achievements num_complete = len([a for a in achievements if a.complete]) txt_final = ba.Lstr( resource='accountSettingsWindow.achievementProgressText', - subs=[('${COUNT}', str(num_complete)), - ('${TOTAL}', str(len(achievements)))]) - self._title_text = ba.textwidget(parent=self.root_widget, - position=(self._width * 0.5, - self._height - 20), - size=(0, 0), - h_align='center', - v_align='center', - scale=0.6, - text=txt_final, - maxwidth=200, - color=(1, 1, 1, 0.4)) + subs=[ + ('${COUNT}', str(num_complete)), + ('${TOTAL}', str(len(achievements))), + ], + ) + self._title_text = ba.textwidget( + parent=self.root_widget, + position=(self._width * 0.5, self._height - 20), + size=(0, 0), + h_align='center', + v_align='center', + scale=0.6, + text=txt_final, + maxwidth=200, + color=(1, 1, 1, 0.4), + ) - self._scrollwidget = ba.scrollwidget(parent=self.root_widget, - size=(self._width - 60, - self._height - 70), - position=(30, 30), - capture_arrows=True, - simple_culling_v=10) + self._scrollwidget = ba.scrollwidget( + parent=self.root_widget, + size=(self._width - 60, self._height - 70), + position=(30, 30), + capture_arrows=True, + simple_culling_v=10, + ) ba.widget(edit=self._scrollwidget, autoselect=True) - ba.containerwidget(edit=self.root_widget, - cancel_button=self._cancel_button) + ba.containerwidget( + edit=self.root_widget, cancel_button=self._cancel_button + ) incr = 36 sub_width = self._width - 90 @@ -85,109 +104,125 @@ class AchievementsWindow(popup.PopupWindow): eq_rsrc = 'coopSelectWindow.powerRankingPointsEqualsText' pts_rsrc = 'coopSelectWindow.powerRankingPointsText' - self._subcontainer = ba.containerwidget(parent=self._scrollwidget, - size=(sub_width, sub_height), - background=False) + self._subcontainer = ba.containerwidget( + parent=self._scrollwidget, + size=(sub_width, sub_height), + background=False, + ) total_pts = 0 for i, ach in enumerate(achievements): complete = ach.complete - ba.textwidget(parent=self._subcontainer, - position=(sub_width * 0.08 - 5, - sub_height - 20 - incr * i), - maxwidth=20, - scale=0.5, - color=(0.6, 0.6, 0.7) if complete else - (0.6, 0.6, 0.7, 0.2), - flatness=1.0, - shadow=0.0, - text=str(i + 1), - size=(0, 0), - h_align='right', - v_align='center') + ba.textwidget( + parent=self._subcontainer, + position=(sub_width * 0.08 - 5, sub_height - 20 - incr * i), + maxwidth=20, + scale=0.5, + color=(0.6, 0.6, 0.7) if complete else (0.6, 0.6, 0.7, 0.2), + flatness=1.0, + shadow=0.0, + text=str(i + 1), + size=(0, 0), + h_align='right', + v_align='center', + ) - ba.imagewidget(parent=self._subcontainer, - position=(sub_width * 0.10 + 1, sub_height - 20 - - incr * i - 9) if complete else - (sub_width * 0.10 - 4, - sub_height - 20 - incr * i - 14), - size=(18, 18) if complete else (27, 27), - opacity=1.0 if complete else 0.3, - color=ach.get_icon_color(complete)[:3], - texture=ach.get_icon_texture(complete)) + ba.imagewidget( + parent=self._subcontainer, + position=(sub_width * 0.10 + 1, sub_height - 20 - incr * i - 9) + if complete + else (sub_width * 0.10 - 4, sub_height - 20 - incr * i - 14), + size=(18, 18) if complete else (27, 27), + opacity=1.0 if complete else 0.3, + color=ach.get_icon_color(complete)[:3], + texture=ach.get_icon_texture(complete), + ) if complete: - ba.imagewidget(parent=self._subcontainer, - position=(sub_width * 0.10 - 4, - sub_height - 25 - incr * i - 9), - size=(28, 28), - color=(2, 1.4, 0), - texture=ba.gettexture('achievementOutline')) - ba.textwidget(parent=self._subcontainer, - position=(sub_width * 0.19, - sub_height - 19 - incr * i + 3), - maxwidth=sub_width * 0.62, - scale=0.6, - flatness=1.0, - shadow=0.0, - color=(1, 1, 1) if complete else (1, 1, 1, 0.2), - text=ach.display_name, - size=(0, 0), - h_align='left', - v_align='center') + ba.imagewidget( + parent=self._subcontainer, + position=( + sub_width * 0.10 - 4, + sub_height - 25 - incr * i - 9, + ), + size=(28, 28), + color=(2, 1.4, 0), + texture=ba.gettexture('achievementOutline'), + ) + ba.textwidget( + parent=self._subcontainer, + position=(sub_width * 0.19, sub_height - 19 - incr * i + 3), + maxwidth=sub_width * 0.62, + scale=0.6, + flatness=1.0, + shadow=0.0, + color=(1, 1, 1) if complete else (1, 1, 1, 0.2), + text=ach.display_name, + size=(0, 0), + h_align='left', + v_align='center', + ) - ba.textwidget(parent=self._subcontainer, - position=(sub_width * 0.19, - sub_height - 19 - incr * i - 10), - maxwidth=sub_width * 0.62, - scale=0.4, - flatness=1.0, - shadow=0.0, - color=(0.83, 0.8, 0.85) if complete else - (0.8, 0.8, 0.8, 0.2), - text=ach.description_full_complete - if complete else ach.description_full, - size=(0, 0), - h_align='left', - v_align='center') + ba.textwidget( + parent=self._subcontainer, + position=(sub_width * 0.19, sub_height - 19 - incr * i - 10), + maxwidth=sub_width * 0.62, + scale=0.4, + flatness=1.0, + shadow=0.0, + color=(0.83, 0.8, 0.85) if complete else (0.8, 0.8, 0.8, 0.2), + text=ach.description_full_complete + if complete + else ach.description_full, + size=(0, 0), + h_align='left', + v_align='center', + ) pts = ach.power_ranking_value - ba.textwidget(parent=self._subcontainer, - position=(sub_width * 0.92, - sub_height - 20 - incr * i), - maxwidth=sub_width * 0.15, - color=(0.7, 0.8, 1.0) if complete else - (0.9, 0.9, 1.0, 0.3), - flatness=1.0, - shadow=0.0, - scale=0.6, - text=ba.Lstr(resource=pts_rsrc, - subs=[('${NUMBER}', str(pts))]), - size=(0, 0), - h_align='center', - v_align='center') + ba.textwidget( + parent=self._subcontainer, + position=(sub_width * 0.92, sub_height - 20 - incr * i), + maxwidth=sub_width * 0.15, + color=(0.7, 0.8, 1.0) if complete else (0.9, 0.9, 1.0, 0.3), + flatness=1.0, + shadow=0.0, + scale=0.6, + text=ba.Lstr(resource=pts_rsrc, subs=[('${NUMBER}', str(pts))]), + size=(0, 0), + h_align='center', + v_align='center', + ) if complete: total_pts += pts - ba.textwidget(parent=self._subcontainer, - position=(sub_width * 1.0, - sub_height - 20 - incr * len(achievements)), - maxwidth=sub_width * 0.5, - scale=0.7, - color=(0.7, 0.8, 1.0), - flatness=1.0, - shadow=0.0, - text=ba.Lstr( - value='${A} ${B}', - subs=[ - ('${A}', - ba.Lstr(resource='coopSelectWindow.totalText')), - ('${B}', - ba.Lstr(resource=eq_rsrc, - subs=[('${NUMBER}', str(total_pts))])) - ]), - size=(0, 0), - h_align='right', - v_align='center') + ba.textwidget( + parent=self._subcontainer, + position=( + sub_width * 1.0, + sub_height - 20 - incr * len(achievements), + ), + maxwidth=sub_width * 0.5, + scale=0.7, + color=(0.7, 0.8, 1.0), + flatness=1.0, + shadow=0.0, + text=ba.Lstr( + value='${A} ${B}', + subs=[ + ('${A}', ba.Lstr(resource='coopSelectWindow.totalText')), + ( + '${B}', + ba.Lstr( + resource=eq_rsrc, + subs=[('${NUMBER}', str(total_pts))], + ), + ), + ], + ), + size=(0, 0), + h_align='right', + v_align='center', + ) def _on_cancel_press(self) -> None: self._transition_out() diff --git a/dist/ba_data/python/bastd/ui/appinvite.py b/dist/ba_data/python/bastd/ui/appinvite.py index 176be36..bd1fd41 100644 --- a/dist/ba_data/python/bastd/ui/appinvite.py +++ b/dist/ba_data/python/bastd/ui/appinvite.py @@ -25,25 +25,36 @@ class AppInviteWindow(ba.Window): self._height = 400 uiscale = ba.app.ui.uiscale - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height), - transition='in_scale', - scale=(1.8 if uiscale is ba.UIScale.SMALL else - 1.35 if uiscale is ba.UIScale.MEDIUM else 1.0))) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height), + transition='in_scale', + scale=( + 1.8 + if uiscale is ba.UIScale.SMALL + else 1.35 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + ) + ) - self._cancel_button = ba.buttonwidget(parent=self._root_widget, - scale=0.8, - position=(60, self._height - 50), - size=(50, 50), - label='', - on_activate_call=self.close, - autoselect=True, - color=(0.4, 0.4, 0.6), - icon=ba.gettexture('crossOut'), - iconscale=1.2) + self._cancel_button = ba.buttonwidget( + parent=self._root_widget, + scale=0.8, + position=(60, self._height - 50), + size=(50, 50), + label='', + on_activate_call=self.close, + autoselect=True, + color=(0.4, 0.4, 0.6), + icon=ba.gettexture('crossOut'), + iconscale=1.2, + ) - ba.containerwidget(edit=self._root_widget, - cancel_button=self._cancel_button) + ba.containerwidget( + edit=self._root_widget, cancel_button=self._cancel_button + ) ba.textwidget( parent=self._root_widget, @@ -59,19 +70,34 @@ class AppInviteWindow(ba.Window): text=ba.Lstr( resource='gatherWindow.earnTicketsForRecommendingAmountText', fallback_resource=( - 'gatherWindow.earnTicketsForRecommendingText'), - subs=[('${COUNT}', - str( - ba.internal.get_v1_account_misc_read_val( - 'friendTryTickets', 300))), - ('${YOU_COUNT}', - str( - ba.internal.get_v1_account_misc_read_val( - 'friendTryAwardTickets', 100)))])) + 'gatherWindow.earnTicketsForRecommendingText' + ), + subs=[ + ( + '${COUNT}', + str( + ba.internal.get_v1_account_misc_read_val( + 'friendTryTickets', 300 + ) + ), + ), + ( + '${YOU_COUNT}', + str( + ba.internal.get_v1_account_misc_read_val( + 'friendTryAwardTickets', 100 + ) + ), + ), + ], + ), + ) - or_text = ba.Lstr(resource='orText', - subs=[('${A}', ''), - ('${B}', '')]).evaluate().strip() + or_text = ( + ba.Lstr(resource='orText', subs=[('${A}', ''), ('${B}', '')]) + .evaluate() + .strip() + ) ba.buttonwidget( parent=self._root_widget, size=(250, 150), @@ -79,18 +105,21 @@ class AppInviteWindow(ba.Window): autoselect=True, button_type='square', label=ba.Lstr(resource='gatherWindow.inviteFriendsText'), - on_activate_call=ba.WeakCall(self._google_invites)) + on_activate_call=ba.WeakCall(self._google_invites), + ) - ba.textwidget(parent=self._root_widget, - size=(0, 0), - position=(self._width * 0.5, self._height * 0.5 - 94), - autoselect=True, - scale=0.9, - h_align='center', - v_align='center', - color=(0.5, 0.5, 0.5), - flatness=1.0, - text=or_text) + ba.textwidget( + parent=self._root_widget, + size=(0, 0), + position=(self._width * 0.5, self._height * 0.5 - 94), + autoselect=True, + scale=0.9, + h_align='center', + v_align='center', + color=(0.5, 0.5, 0.5), + flatness=1.0, + text=or_text, + ) ba.buttonwidget( parent=self._root_widget, @@ -101,16 +130,18 @@ class AppInviteWindow(ba.Window): textcolor=(0.7, 0.7, 0.8), text_scale=0.8, label=ba.Lstr(resource='gatherWindow.appInviteSendACodeText'), - on_activate_call=ba.WeakCall(self._send_code)) + on_activate_call=ba.WeakCall(self._send_code), + ) # kick off a transaction to get our code ba.internal.add_transaction( { 'type': 'FRIEND_PROMO_CODE_REQUEST', 'ali': False, - 'expire_time': time.time() + 20 + 'expire_time': time.time() + 20, }, - callback=ba.WeakCall(self._on_code_result)) + callback=ba.WeakCall(self._on_code_result), + ) ba.internal.run_transactions() def _on_code_result(self, result: dict[str, Any] | None) -> None: @@ -122,24 +153,33 @@ class AppInviteWindow(ba.Window): def _google_invites(self) -> None: if self._data is None: - ba.screenmessage(ba.Lstr( - resource='getTicketsWindow.unavailableTemporarilyText'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr(resource='getTicketsWindow.unavailableTemporarilyText'), + color=(1, 0, 0), + ) ba.playsound(ba.getsound('error')) return if ba.internal.get_v1_account_state() == 'signed_in': ba.set_analytics_screen('App Invite UI') ba.internal.show_app_invite( - ba.Lstr(resource='gatherWindow.appInviteTitleText', - subs=[('${APP_NAME}', ba.Lstr(resource='titleText')) - ]).evaluate(), - ba.Lstr(resource='gatherWindow.appInviteMessageText', - subs=[('${COUNT}', str(self._data['tickets'])), - ('${NAME}', - ba.internal.get_v1_account_name().split()[0]), - ('${APP_NAME}', ba.Lstr(resource='titleText')) - ]).evaluate(), self._data['code']) + ba.Lstr( + resource='gatherWindow.appInviteTitleText', + subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))], + ).evaluate(), + ba.Lstr( + resource='gatherWindow.appInviteMessageText', + subs=[ + ('${COUNT}', str(self._data['tickets'])), + ( + '${NAME}', + ba.internal.get_v1_account_name().split()[0], + ), + ('${APP_NAME}', ba.Lstr(resource='titleText')), + ], + ).evaluate(), + self._data['code'], + ) else: ba.playsound(ba.getsound('error')) @@ -153,32 +193,44 @@ class ShowFriendCodeWindow(ba.Window): def __init__(self, data: dict[str, Any]): from ba.internal import is_browser_likely_available + ba.set_analytics_screen('Friend Promo Code') self._width = 650 self._height = 400 uiscale = ba.app.ui.uiscale - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height), - color=(0.45, 0.63, 0.15), - transition='in_scale', - scale=(1.7 if uiscale is ba.UIScale.SMALL else - 1.35 if uiscale is ba.UIScale.MEDIUM else 1.0))) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height), + color=(0.45, 0.63, 0.15), + transition='in_scale', + scale=( + 1.7 + if uiscale is ba.UIScale.SMALL + else 1.35 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + ) + ) self._data = copy.deepcopy(data) ba.playsound(ba.getsound('cashRegister')) ba.playsound(ba.getsound('swish')) - self._cancel_button = ba.buttonwidget(parent=self._root_widget, - scale=0.7, - position=(50, self._height - 50), - size=(60, 60), - label='', - on_activate_call=self.close, - autoselect=True, - color=(0.45, 0.63, 0.15), - icon=ba.gettexture('crossOut'), - iconscale=1.2) - ba.containerwidget(edit=self._root_widget, - cancel_button=self._cancel_button) + self._cancel_button = ba.buttonwidget( + parent=self._root_widget, + scale=0.7, + position=(50, self._height - 50), + size=(60, 60), + label='', + on_activate_call=self.close, + autoselect=True, + color=(0.45, 0.63, 0.15), + icon=ba.gettexture('crossOut'), + iconscale=1.2, + ) + ba.containerwidget( + edit=self._root_widget, cancel_button=self._cancel_button + ) ba.textwidget( parent=self._root_widget, @@ -190,23 +242,27 @@ class ShowFriendCodeWindow(ba.Window): h_align='center', v_align='center', text=ba.Lstr(resource='gatherWindow.shareThisCodeWithFriendsText'), - maxwidth=self._width * 0.85) + maxwidth=self._width * 0.85, + ) - ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, self._height * 0.645), - size=(0, 0), - color=(1.0, 3.0, 1.0), - scale=2.0, - h_align='center', - v_align='center', - text=data['code'], - maxwidth=self._width * 0.85) + ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, self._height * 0.645), + size=(0, 0), + color=(1.0, 3.0, 1.0), + scale=2.0, + h_align='center', + v_align='center', + text=data['code'], + maxwidth=self._width * 0.85, + ) award_str: str | ba.Lstr | None if self._data['awardTickets'] != 0: award_str = ba.Lstr( resource='gatherWindow.friendPromoCodeAwardText', - subs=[('${COUNT}', str(self._data['awardTickets']))]) + subs=[('${COUNT}', str(self._data['awardTickets']))], + ) else: award_str = '' ba.textwidget( @@ -221,80 +277,127 @@ class ShowFriendCodeWindow(ba.Window): text=ba.Lstr( value='${A}\n${B}\n${C}\n${D}', subs=[ - ('${A}', - ba.Lstr( - resource='gatherWindow.friendPromoCodeRedeemLongText', - subs=[('${COUNT}', str(self._data['tickets'])), - ('${MAX_USES}', - str(self._data['usesRemaining']))])), - ('${B}', - ba.Lstr(resource=( - 'gatherWindow.friendPromoCodeWhereToEnterText'))), + ( + '${A}', + ba.Lstr( + resource=( + 'gatherWindow.friendPromoCodeRedeemLongText' + ), + subs=[ + ('${COUNT}', str(self._data['tickets'])), + ( + '${MAX_USES}', + str(self._data['usesRemaining']), + ), + ], + ), + ), + ( + '${B}', + ba.Lstr( + resource=( + 'gatherWindow.friendPromoCodeWhereToEnterText' + ) + ), + ), ('${C}', award_str), - ('${D}', - ba.Lstr(resource='gatherWindow.friendPromoCodeExpireText', - subs=[('${EXPIRE_HOURS}', - str(self._data['expireHours']))])) - ]), + ( + '${D}', + ba.Lstr( + resource='gatherWindow.friendPromoCodeExpireText', + subs=[ + ( + '${EXPIRE_HOURS}', + str(self._data['expireHours']), + ) + ], + ), + ), + ], + ), maxwidth=self._width * 0.9, - max_height=self._height * 0.35) + max_height=self._height * 0.35, + ) if is_browser_likely_available(): xoffs = 0 - ba.buttonwidget(parent=self._root_widget, - size=(200, 40), - position=(self._width * 0.5 - 100 + xoffs, 39), - autoselect=True, - label=ba.Lstr(resource='gatherWindow.emailItText'), - on_activate_call=ba.WeakCall(self._email)) + ba.buttonwidget( + parent=self._root_widget, + size=(200, 40), + position=(self._width * 0.5 - 100 + xoffs, 39), + autoselect=True, + label=ba.Lstr(resource='gatherWindow.emailItText'), + on_activate_call=ba.WeakCall(self._email), + ) def _google_invites(self) -> None: ba.set_analytics_screen('App Invite UI') ba.internal.show_app_invite( - ba.Lstr(resource='gatherWindow.appInviteTitleText', - subs=[('${APP_NAME}', ba.Lstr(resource='titleText')) - ]).evaluate(), - ba.Lstr(resource='gatherWindow.appInviteMessageText', - subs=[('${COUNT}', str(self._data['tickets'])), - ('${NAME}', - ba.internal.get_v1_account_name().split()[0]), - ('${APP_NAME}', ba.Lstr(resource='titleText')) - ]).evaluate(), self._data['code']) + ba.Lstr( + resource='gatherWindow.appInviteTitleText', + subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))], + ).evaluate(), + ba.Lstr( + resource='gatherWindow.appInviteMessageText', + subs=[ + ('${COUNT}', str(self._data['tickets'])), + ('${NAME}', ba.internal.get_v1_account_name().split()[0]), + ('${APP_NAME}', ba.Lstr(resource='titleText')), + ], + ).evaluate(), + self._data['code'], + ) def _email(self) -> None: import urllib.parse # If somehow we got signed out. if ba.internal.get_v1_account_state() != 'signed_in': - ba.screenmessage(ba.Lstr(resource='notSignedInText'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr(resource='notSignedInText'), color=(1, 0, 0) + ) ba.playsound(ba.getsound('error')) return ba.set_analytics_screen('Email Friend Code') - subject = (ba.Lstr(resource='gatherWindow.friendHasSentPromoCodeText'). - evaluate().replace( - '${NAME}', ba.internal.get_v1_account_name()).replace( - '${APP_NAME}', - ba.Lstr(resource='titleText').evaluate()).replace( - '${COUNT}', str(self._data['tickets']))) - body = (ba.Lstr(resource='gatherWindow.youHaveBeenSentAPromoCodeText'). - evaluate().replace('${APP_NAME}', - ba.Lstr(resource='titleText').evaluate()) + - '\n\n' + str(self._data['code']) + '\n\n') + subject = ( + ba.Lstr(resource='gatherWindow.friendHasSentPromoCodeText') + .evaluate() + .replace('${NAME}', ba.internal.get_v1_account_name()) + .replace('${APP_NAME}', ba.Lstr(resource='titleText').evaluate()) + .replace('${COUNT}', str(self._data['tickets'])) + ) + body = ( + ba.Lstr(resource='gatherWindow.youHaveBeenSentAPromoCodeText') + .evaluate() + .replace('${APP_NAME}', ba.Lstr(resource='titleText').evaluate()) + + '\n\n' + + str(self._data['code']) + + '\n\n' + ) body += ( - (ba.Lstr(resource='gatherWindow.friendPromoCodeRedeemShortText'). - evaluate().replace('${COUNT}', str(self._data['tickets']))) + - '\n\n' + - ba.Lstr(resource='gatherWindow.friendPromoCodeInstructionsText'). - evaluate().replace('${APP_NAME}', - ba.Lstr(resource='titleText').evaluate()) + - '\n' + ba.Lstr(resource='gatherWindow.friendPromoCodeExpireText'). - evaluate().replace('${EXPIRE_HOURS}', str( - self._data['expireHours'])) + '\n' + - ba.Lstr(resource='enjoyText').evaluate()) - ba.open_url('mailto:?subject=' + urllib.parse.quote(subject) + - '&body=' + urllib.parse.quote(body)) + ( + ba.Lstr(resource='gatherWindow.friendPromoCodeRedeemShortText') + .evaluate() + .replace('${COUNT}', str(self._data['tickets'])) + ) + + '\n\n' + + ba.Lstr(resource='gatherWindow.friendPromoCodeInstructionsText') + .evaluate() + .replace('${APP_NAME}', ba.Lstr(resource='titleText').evaluate()) + + '\n' + + ba.Lstr(resource='gatherWindow.friendPromoCodeExpireText') + .evaluate() + .replace('${EXPIRE_HOURS}', str(self._data['expireHours'])) + + '\n' + + ba.Lstr(resource='enjoyText').evaluate() + ) + ba.open_url( + 'mailto:?subject=' + + urllib.parse.quote(subject) + + '&body=' + + urllib.parse.quote(body) + ) def close(self) -> None: """Close the window.""" @@ -304,9 +407,12 @@ class ShowFriendCodeWindow(ba.Window): def handle_app_invites_press(force_code: bool = False) -> None: """(internal)""" app = ba.app - do_app_invites = (app.platform == 'android' and app.subplatform == 'google' - and ba.internal.get_v1_account_misc_read_val( - 'enableAppInvites', False) and not app.on_tv) + do_app_invites = ( + app.platform == 'android' + and app.subplatform == 'google' + and ba.internal.get_v1_account_misc_read_val('enableAppInvites', False) + and not app.on_tv + ) if force_code: do_app_invites = False @@ -316,13 +422,15 @@ def handle_app_invites_press(force_code: bool = False) -> None: else: ba.screenmessage( ba.Lstr(resource='gatherWindow.requestingAPromoCodeText'), - color=(0, 1, 0)) + color=(0, 1, 0), + ) def handle_result(result: dict[str, Any] | None) -> None: with ba.Context('ui'): if result is None: - ba.screenmessage(ba.Lstr(resource='errorText'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr(resource='errorText'), color=(1, 0, 0) + ) ba.playsound(ba.getsound('error')) else: ShowFriendCodeWindow(result) @@ -331,7 +439,8 @@ def handle_app_invites_press(force_code: bool = False) -> None: { 'type': 'FRIEND_PROMO_CODE_REQUEST', 'ali': False, - 'expire_time': time.time() + 10 + 'expire_time': time.time() + 10, }, - callback=handle_result) + callback=handle_result, + ) ba.internal.run_transactions() diff --git a/dist/ba_data/python/bastd/ui/characterpicker.py b/dist/ba_data/python/bastd/ui/characterpicker.py index 62f78bc..490c062 100644 --- a/dist/ba_data/python/bastd/ui/characterpicker.py +++ b/dist/ba_data/python/bastd/ui/characterpicker.py @@ -18,22 +18,30 @@ if TYPE_CHECKING: class CharacterPicker(popup.PopupWindow): """Popup window for selecting characters.""" - def __init__(self, - parent: ba.Widget, - position: tuple[float, float] = (0.0, 0.0), - delegate: Any = None, - scale: float | None = None, - offset: tuple[float, float] = (0.0, 0.0), - tint_color: Sequence[float] = (1.0, 1.0, 1.0), - tint2_color: Sequence[float] = (1.0, 1.0, 1.0), - selected_character: str | None = None): + def __init__( + self, + parent: ba.Widget, + position: tuple[float, float] = (0.0, 0.0), + delegate: Any = None, + scale: float | None = None, + offset: tuple[float, float] = (0.0, 0.0), + tint_color: Sequence[float] = (1.0, 1.0, 1.0), + tint2_color: Sequence[float] = (1.0, 1.0, 1.0), + selected_character: str | None = None, + ): # pylint: disable=too-many-locals from bastd.actor import spazappearance + del parent # unused here uiscale = ba.app.ui.uiscale if scale is None: - scale = (1.85 if uiscale is ba.UIScale.SMALL else - 1.65 if uiscale is ba.UIScale.MEDIUM else 1.23) + scale = ( + 1.85 + if uiscale is ba.UIScale.SMALL + else 1.65 + if uiscale is ba.UIScale.MEDIUM + else 1.23 + ) self._delegate = delegate self._transitioning_out = False @@ -60,49 +68,60 @@ class CharacterPicker(popup.PopupWindow): button_buffer_h = 10 button_buffer_v = 15 - self._width = (10 + columns * (button_width + 2 * button_buffer_h) * - (1.0 / 0.95) * (1.0 / 0.8)) - self._height = self._width * (0.8 - if uiscale is ba.UIScale.SMALL else 1.06) + self._width = 10 + columns * (button_width + 2 * button_buffer_h) * ( + 1.0 / 0.95 + ) * (1.0 / 0.8) + self._height = self._width * ( + 0.8 if uiscale is ba.UIScale.SMALL else 1.06 + ) self._scroll_width = self._width * 0.8 self._scroll_height = self._height * 0.8 - self._scroll_position = ((self._width - self._scroll_width) * 0.5, - (self._height - self._scroll_height) * 0.5) + self._scroll_position = ( + (self._width - self._scroll_width) * 0.5, + (self._height - self._scroll_height) * 0.5, + ) # creates our _root_widget - popup.PopupWindow.__init__(self, - position=position, - size=(self._width, self._height), - scale=scale, - bg_color=(0.5, 0.5, 0.5), - offset=offset, - focus_position=self._scroll_position, - focus_size=(self._scroll_width, - self._scroll_height)) + popup.PopupWindow.__init__( + self, + position=position, + size=(self._width, self._height), + scale=scale, + bg_color=(0.5, 0.5, 0.5), + offset=offset, + focus_position=self._scroll_position, + focus_size=(self._scroll_width, self._scroll_height), + ) - self._scrollwidget = ba.scrollwidget(parent=self.root_widget, - size=(self._scroll_width, - self._scroll_height), - color=(0.55, 0.55, 0.55), - highlight=False, - position=self._scroll_position) + self._scrollwidget = ba.scrollwidget( + parent=self.root_widget, + size=(self._scroll_width, self._scroll_height), + color=(0.55, 0.55, 0.55), + highlight=False, + position=self._scroll_position, + ) ba.containerwidget(edit=self._scrollwidget, claims_left_right=True) self._sub_width = self._scroll_width * 0.95 - self._sub_height = 5 + rows * (button_height + - 2 * button_buffer_v) + 100 - self._subcontainer = ba.containerwidget(parent=self._scrollwidget, - size=(self._sub_width, - self._sub_height), - background=False) + self._sub_height = ( + 5 + rows * (button_height + 2 * button_buffer_v) + 100 + ) + self._subcontainer = ba.containerwidget( + parent=self._scrollwidget, + size=(self._sub_width, self._sub_height), + background=False, + ) index = 0 mask_texture = ba.gettexture('characterIconMask') for y in range(rows): for x in range(columns): - pos = (x * (button_width + 2 * button_buffer_h) + - button_buffer_h, self._sub_height - (y + 1) * - (button_height + 2 * button_buffer_v) + 12) + pos = ( + x * (button_width + 2 * button_buffer_h) + button_buffer_h, + self._sub_height + - (y + 1) * (button_height + 2 * button_buffer_v) + + 12, + ) btn = ba.buttonwidget( parent=self._subcontainer, button_type='square', @@ -115,27 +134,33 @@ class CharacterPicker(popup.PopupWindow): color=(1, 1, 1), tint_color=tint_color, tint2_color=tint2_color, - on_activate_call=ba.Call(self._select_character, - self._spazzes[index]), - position=pos) + on_activate_call=ba.Call( + self._select_character, self._spazzes[index] + ), + position=pos, + ) ba.widget(edit=btn, show_buffer_top=60, show_buffer_bottom=60) if self._spazzes[index] == selected_character: - ba.containerwidget(edit=self._subcontainer, - selected_child=btn, - visible_child=btn) - name = ba.Lstr(translate=('characterNames', - self._spazzes[index])) - ba.textwidget(parent=self._subcontainer, - text=name, - position=(pos[0] + button_width * 0.5, - pos[1] - 12), - size=(0, 0), - scale=0.5, - maxwidth=button_width, - draw_controller=btn, - h_align='center', - v_align='center', - color=(0.8, 0.8, 0.8, 0.8)) + ba.containerwidget( + edit=self._subcontainer, + selected_child=btn, + visible_child=btn, + ) + name = ba.Lstr( + translate=('characterNames', self._spazzes[index]) + ) + ba.textwidget( + parent=self._subcontainer, + text=name, + position=(pos[0] + button_width * 0.5, pos[1] - 12), + size=(0, 0), + scale=0.5, + maxwidth=button_width, + draw_controller=btn, + h_align='center', + v_align='center', + color=(0.8, 0.8, 0.8, 0.8), + ) index += 1 if index >= count: @@ -150,19 +175,23 @@ class CharacterPicker(popup.PopupWindow): on_activate_call=self._on_store_press, color=(0.6, 0.6, 0.6), textcolor=(0.8, 0.8, 0.8), - autoselect=True) + autoselect=True, + ) ba.widget(edit=btn, show_buffer_top=30, show_buffer_bottom=30) def _on_store_press(self) -> None: from bastd.ui.account import show_sign_in_prompt from bastd.ui.store.browser import StoreBrowserWindow + if ba.internal.get_v1_account_state() != 'signed_in': show_sign_in_prompt() return self._transition_out() - StoreBrowserWindow(modal=True, - show_tab=StoreBrowserWindow.TabID.CHARACTERS, - origin_widget=self._get_more_characters_button) + StoreBrowserWindow( + modal=True, + show_tab=StoreBrowserWindow.TabID.CHARACTERS, + origin_widget=self._get_more_characters_button, + ) def _select_character(self, character: str) -> None: if self._delegate is not None: diff --git a/dist/ba_data/python/bastd/ui/colorpicker.py b/dist/ba_data/python/bastd/ui/colorpicker.py index 13384e8..378f56f 100644 --- a/dist/ba_data/python/bastd/ui/colorpicker.py +++ b/dist/ba_data/python/bastd/ui/colorpicker.py @@ -19,14 +19,16 @@ class ColorPicker(PopupWindow): Passes the color to the delegate's color_picker_selected_color() method. """ - def __init__(self, - parent: ba.Widget, - position: tuple[float, float], - initial_color: Sequence[float] = (1.0, 1.0, 1.0), - delegate: Any = None, - scale: float | None = None, - offset: tuple[float, float] = (0.0, 0.0), - tag: Any = ''): + def __init__( + self, + parent: ba.Widget, + position: tuple[float, float], + initial_color: Sequence[float] = (1.0, 1.0, 1.0), + delegate: Any = None, + scale: float | None = None, + offset: tuple[float, float] = (0.0, 0.0), + tag: Any = '', + ): # pylint: disable=too-many-locals from ba.internal import get_player_colors @@ -36,8 +38,13 @@ class ColorPicker(PopupWindow): uiscale = ba.app.ui.uiscale if scale is None: - scale = (2.3 if uiscale is ba.UIScale.SMALL else - 1.65 if uiscale is ba.UIScale.MEDIUM else 1.23) + scale = ( + 2.3 + if uiscale is ba.UIScale.SMALL + else 1.65 + if uiscale is ba.UIScale.MEDIUM + else 1.23 + ) self._parent = parent self._position = position self._scale = scale @@ -48,14 +55,16 @@ class ColorPicker(PopupWindow): self._initial_color = initial_color # Create our _root_widget. - PopupWindow.__init__(self, - position=position, - size=(210, 240), - scale=scale, - focus_position=(10, 10), - focus_size=(190, 220), - bg_color=(0.5, 0.5, 0.5), - offset=offset) + PopupWindow.__init__( + self, + position=position, + size=(210, 240), + scale=scale, + focus_position=(10, 10), + focus_size=(190, 220), + bg_color=(0.5, 0.5, 0.5), + offset=offset, + ) rows: list[list[ba.Widget]] = [] closest_dist = 9999.0 closest = (0, 0) @@ -64,22 +73,25 @@ class ColorPicker(PopupWindow): rows.append(row) for x in range(4): color = self.colors[y][x] - dist = (abs(color[0] - initial_color[0]) + - abs(color[1] - initial_color[1]) + - abs(color[2] - initial_color[2])) + dist = ( + abs(color[0] - initial_color[0]) + + abs(color[1] - initial_color[1]) + + abs(color[2] - initial_color[2]) + ) if dist < closest_dist: closest = (x, y) closest_dist = dist - btn = ba.buttonwidget(parent=self.root_widget, - position=(22 + 45 * x, 185 - 45 * y), - size=(35, 40), - label='', - button_type='square', - on_activate_call=ba.WeakCall( - self._select, x, y), - autoselect=True, - color=color, - extra_touch_border_scale=0.0) + btn = ba.buttonwidget( + parent=self.root_widget, + position=(22 + 45 * x, 185 - 45 * y), + size=(35, 40), + label='', + button_type='square', + on_activate_call=ba.WeakCall(self._select, x, y), + autoselect=True, + color=color, + extra_touch_border_scale=0.0, + ) row.append(btn) other_button = ba.buttonwidget( parent=self.root_widget, @@ -88,27 +100,35 @@ class ColorPicker(PopupWindow): text_scale=0.5, textcolor=(0.8, 0.8, 0.8), size=(120, 30), - label=ba.Lstr(resource='otherText', - fallback_resource='coopSelectWindow.customText'), + label=ba.Lstr( + resource='otherText', + fallback_resource='coopSelectWindow.customText', + ), autoselect=True, - on_activate_call=ba.WeakCall(self._select_other)) + on_activate_call=ba.WeakCall(self._select_other), + ) # Custom colors are limited to pro currently. if not ba.app.accounts_v1.have_pro(): - ba.imagewidget(parent=self.root_widget, - position=(50, 12), - size=(30, 30), - texture=ba.gettexture('lock'), - draw_controller=other_button) + ba.imagewidget( + parent=self.root_widget, + position=(50, 12), + size=(30, 30), + texture=ba.gettexture('lock'), + draw_controller=other_button, + ) # If their color is close to one of our swatches, select it. # Otherwise select 'other'. if closest_dist < 0.03: - ba.containerwidget(edit=self.root_widget, - selected_child=rows[closest[1]][closest[0]]) + ba.containerwidget( + edit=self.root_widget, + selected_child=rows[closest[1]][closest[0]], + ) else: - ba.containerwidget(edit=self.root_widget, - selected_child=other_button) + ba.containerwidget( + edit=self.root_widget, selected_child=other_button + ) def get_tag(self) -> Any: """Return this popup's tag.""" @@ -122,13 +142,15 @@ class ColorPicker(PopupWindow): purchase.PurchaseWindow(items=['pro']) self._transition_out() return - ColorPickerExact(parent=self._parent, - position=self._position, - initial_color=self._initial_color, - delegate=self._delegate, - scale=self._scale, - offset=self._offset, - tag=self._tag) + ColorPickerExact( + parent=self._parent, + position=self._position, + initial_color=self._initial_color, + delegate=self._delegate, + scale=self._scale, + offset=self._offset, + tag=self._tag, + ) # New picker now 'owns' the delegate; we shouldn't send it any # more messages. @@ -154,34 +176,43 @@ class ColorPicker(PopupWindow): class ColorPickerExact(PopupWindow): - """ pops up a ui to select from a set of colors. - passes the color to the delegate's color_picker_selected_color() method """ + """pops up a ui to select from a set of colors. + passes the color to the delegate's color_picker_selected_color() method""" - def __init__(self, - parent: ba.Widget, - position: tuple[float, float], - initial_color: Sequence[float] = (1.0, 1.0, 1.0), - delegate: Any = None, - scale: float | None = None, - offset: tuple[float, float] = (0.0, 0.0), - tag: Any = ''): + def __init__( + self, + parent: ba.Widget, + position: tuple[float, float], + initial_color: Sequence[float] = (1.0, 1.0, 1.0), + delegate: Any = None, + scale: float | None = None, + offset: tuple[float, float] = (0.0, 0.0), + tag: Any = '', + ): # pylint: disable=too-many-locals del parent # Unused var. from ba.internal import get_player_colors + c_raw = get_player_colors() assert len(c_raw) == 16 self.colors = [c_raw[0:4], c_raw[4:8], c_raw[8:12], c_raw[12:16]] uiscale = ba.app.ui.uiscale if scale is None: - scale = (2.3 if uiscale is ba.UIScale.SMALL else - 1.65 if uiscale is ba.UIScale.MEDIUM else 1.23) + scale = ( + 2.3 + if uiscale is ba.UIScale.SMALL + else 1.65 + if uiscale is ba.UIScale.MEDIUM + else 1.23 + ) self._delegate = delegate self._transitioning_out = False self._tag = tag self._color = list(initial_color) - self._last_press_time = ba.time(ba.TimeType.REAL, - ba.TimeFormat.MILLISECONDS) + self._last_press_time = ba.time( + ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS + ) self._last_press_color_name: str | None = None self._last_press_increasing: bool | None = None self._change_speed = 1.0 @@ -189,60 +220,71 @@ class ColorPickerExact(PopupWindow): height = 240.0 # Creates our _root_widget. - PopupWindow.__init__(self, - position=position, - size=(width, height), - scale=scale, - focus_position=(10, 10), - focus_size=(width - 20, height - 20), - bg_color=(0.5, 0.5, 0.5), - offset=offset) - self._swatch = ba.imagewidget(parent=self.root_widget, - position=(width * 0.5 - 50, height - 70), - size=(100, 70), - texture=ba.gettexture('buttonSquare'), - color=(1, 0, 0)) + PopupWindow.__init__( + self, + position=position, + size=(width, height), + scale=scale, + focus_position=(10, 10), + focus_size=(width - 20, height - 20), + bg_color=(0.5, 0.5, 0.5), + offset=offset, + ) + self._swatch = ba.imagewidget( + parent=self.root_widget, + position=(width * 0.5 - 50, height - 70), + size=(100, 70), + texture=ba.gettexture('buttonSquare'), + color=(1, 0, 0), + ) x = 50 y = height - 90 self._label_r: ba.Widget self._label_g: ba.Widget self._label_b: ba.Widget - for color_name, color_val in [('r', (1, 0.15, 0.15)), - ('g', (0.15, 1, 0.15)), - ('b', (0.15, 0.15, 1))]: - txt = ba.textwidget(parent=self.root_widget, - position=(x - 10, y), - size=(0, 0), - h_align='center', - color=color_val, - v_align='center', - text='0.12') + for color_name, color_val in [ + ('r', (1, 0.15, 0.15)), + ('g', (0.15, 1, 0.15)), + ('b', (0.15, 0.15, 1)), + ]: + txt = ba.textwidget( + parent=self.root_widget, + position=(x - 10, y), + size=(0, 0), + h_align='center', + color=color_val, + v_align='center', + text='0.12', + ) setattr(self, '_label_' + color_name, txt) for b_label, bhval, binc in [('-', 30, False), ('+', 75, True)]: - ba.buttonwidget(parent=self.root_widget, - position=(x + bhval, y - 15), - scale=0.8, - repeat=True, - text_scale=1.3, - size=(40, 40), - label=b_label, - autoselect=True, - enable_sound=False, - on_activate_call=ba.WeakCall( - self._color_change_press, color_name, - binc)) + ba.buttonwidget( + parent=self.root_widget, + position=(x + bhval, y - 15), + scale=0.8, + repeat=True, + text_scale=1.3, + size=(40, 40), + label=b_label, + autoselect=True, + enable_sound=False, + on_activate_call=ba.WeakCall( + self._color_change_press, color_name, binc + ), + ) y -= 42 - btn = ba.buttonwidget(parent=self.root_widget, - position=(width * 0.5 - 40, 10), - size=(80, 30), - text_scale=0.6, - color=(0.6, 0.6, 0.6), - textcolor=(0.7, 0.7, 0.7), - label=ba.Lstr(resource='doneText'), - on_activate_call=ba.WeakCall( - self._transition_out), - autoselect=True) + btn = ba.buttonwidget( + parent=self.root_widget, + position=(width * 0.5 - 40, 10), + size=(80, 30), + text_scale=0.6, + color=(0.6, 0.6, 0.6), + textcolor=(0.7, 0.7, 0.7), + label=ba.Lstr(resource='doneText'), + on_activate_call=ba.WeakCall(self._transition_out), + autoselect=True, + ) ba.containerwidget(edit=self.root_widget, start_button=btn) # Unlike the swatch picker, we stay open and constantly push our @@ -268,8 +310,11 @@ class ColorPickerExact(PopupWindow): # If we get rapid-fire presses, eventually start moving faster. current_time = ba.time(ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS) since_last = current_time - self._last_press_time - if (since_last < 200 and self._last_press_color_name == color_name - and self._last_press_increasing == increasing): + if ( + since_last < 200 + and self._last_press_color_name == color_name + and self._last_press_increasing == increasing + ): self._change_speed += 0.25 else: self._change_speed = 1.0 @@ -280,7 +325,8 @@ class ColorPickerExact(PopupWindow): color_index = ('r', 'g', 'b').index(color_name) offs = int(self._change_speed) * (0.01 if increasing else -0.01) self._color[color_index] = max( - 0.0, min(1.0, self._color[color_index] + offs)) + 0.0, min(1.0, self._color[color_index] + offs) + ) self._update_for_color() def get_tag(self) -> Any: diff --git a/dist/ba_data/python/bastd/ui/config.py b/dist/ba_data/python/bastd/ui/config.py index e8a6cc1..b200866 100644 --- a/dist/ba_data/python/bastd/ui/config.py +++ b/dist/ba_data/python/bastd/ui/config.py @@ -22,16 +22,18 @@ class ConfigCheckBox: widget: ba.Widget """The underlying ba.Widget instance.""" - def __init__(self, - parent: ba.Widget, - configkey: str, - position: tuple[float, float], - size: tuple[float, float], - displayname: str | ba.Lstr | None = None, - scale: float | None = None, - maxwidth: float | None = None, - autoselect: bool = True, - value_change_call: Callable[[Any], Any] | None = None): + def __init__( + self, + parent: ba.Widget, + configkey: str, + position: tuple[float, float], + size: tuple[float, float], + displayname: str | ba.Lstr | None = None, + scale: float | None = None, + maxwidth: float | None = None, + autoselect: bool = True, + value_change_call: Callable[[Any], Any] | None = None, + ): if displayname is None: displayname = configkey self._value_change_call = value_change_call @@ -46,7 +48,8 @@ class ConfigCheckBox: value=ba.app.config.resolve(configkey), on_value_change_call=self._value_changed, scale=scale, - maxwidth=maxwidth) + maxwidth=maxwidth, + ) # complain if we outlive our checkbox ba.uicleanupcheck(self, self.widget) @@ -77,18 +80,20 @@ class ConfigNumberEdit: plusbutton: ba.Widget """The button widget used to increase the value.""" - def __init__(self, - parent: ba.Widget, - configkey: str, - position: tuple[float, float], - minval: float = 0.0, - maxval: float = 100.0, - increment: float = 1.0, - callback: Callable[[float], Any] | None = None, - xoffset: float = 0.0, - displayname: str | ba.Lstr | None = None, - changesound: bool = True, - textscale: float = 1.0): + def __init__( + self, + parent: ba.Widget, + configkey: str, + position: tuple[float, float], + minval: float = 0.0, + maxval: float = 100.0, + increment: float = 1.0, + callback: Callable[[float], Any] | None = None, + xoffset: float = 0.0, + displayname: str | ba.Lstr | None = None, + changesound: bool = True, + textscale: float = 1.0, + ): if displayname is None: displayname = configkey @@ -99,24 +104,28 @@ class ConfigNumberEdit: self._callback = callback self._value = ba.app.config.resolve(configkey) - self.nametext = ba.textwidget(parent=parent, - position=position, - size=(100, 30), - text=displayname, - maxwidth=160 + xoffset, - color=(0.8, 0.8, 0.8, 1.0), - h_align='left', - v_align='center', - scale=textscale) - self.valuetext = ba.textwidget(parent=parent, - position=(246 + xoffset, position[1]), - size=(60, 28), - editable=False, - color=(0.3, 1.0, 0.3, 1.0), - h_align='right', - v_align='center', - text=str(self._value), - padding=2) + self.nametext = ba.textwidget( + parent=parent, + position=position, + size=(100, 30), + text=displayname, + maxwidth=160 + xoffset, + color=(0.8, 0.8, 0.8, 1.0), + h_align='left', + v_align='center', + scale=textscale, + ) + self.valuetext = ba.textwidget( + parent=parent, + position=(246 + xoffset, position[1]), + size=(60, 28), + editable=False, + color=(0.3, 1.0, 0.3, 1.0), + h_align='right', + v_align='center', + text=str(self._value), + padding=2, + ) self.minusbutton = ba.buttonwidget( parent=parent, position=(330 + xoffset, position[1]), @@ -125,16 +134,18 @@ class ConfigNumberEdit: autoselect=True, on_activate_call=ba.Call(self._down), repeat=True, - enable_sound=changesound) - self.plusbutton = ba.buttonwidget(parent=parent, - position=(380 + xoffset, - position[1]), - size=(28, 28), - label='+', - autoselect=True, - on_activate_call=ba.Call(self._up), - repeat=True, - enable_sound=changesound) + enable_sound=changesound, + ) + self.plusbutton = ba.buttonwidget( + parent=parent, + position=(380 + xoffset, position[1]), + size=(28, 28), + label='+', + autoselect=True, + on_activate_call=ba.Call(self._up), + repeat=True, + enable_sound=changesound, + ) # Complain if we outlive our widgets. ba.uicleanupcheck(self, self.nametext) self._update_display() diff --git a/dist/ba_data/python/bastd/ui/configerror.py b/dist/ba_data/python/bastd/ui/configerror.py index 26ffdf6..129e5ea 100644 --- a/dist/ba_data/python/bastd/ui/configerror.py +++ b/dist/ba_data/python/bastd/ui/configerror.py @@ -20,7 +20,8 @@ class ConfigErrorWindow(ba.Window): self._config_file_path = ba.app.config_file_path width = 800 super().__init__( - ba.containerwidget(size=(width, 300), transition='in_right')) + ba.containerwidget(size=(width, 300), transition='in_right') + ) padding = 20 ba.textwidget( parent=self._root_widget, @@ -29,32 +30,43 @@ class ConfigErrorWindow(ba.Window): h_align='center', v_align='top', scale=0.73, - text=(f'Error reading {ba.internal.appnameupper()} config file' - ':\n\n\nCheck the console' - ' (press ~ twice) for details.\n\nWould you like to quit and' - ' try to fix it by hand\nor overwrite it with defaults?\n\n' - '(high scores, player profiles, etc will be lost if you' - ' overwrite)')) - ba.textwidget(parent=self._root_widget, - position=(padding, 198), - size=(width - 2 * padding, 100 - 2 * padding), - h_align='center', - v_align='top', - scale=0.5, - text=self._config_file_path) - quit_button = ba.buttonwidget(parent=self._root_widget, - position=(35, 30), - size=(240, 54), - label='Quit and Edit', - on_activate_call=self._quit) - ba.buttonwidget(parent=self._root_widget, - position=(width - 370, 30), - size=(330, 54), - label='Overwrite with Defaults', - on_activate_call=self._defaults) - ba.containerwidget(edit=self._root_widget, - cancel_button=quit_button, - selected_child=quit_button) + text=( + f'Error reading {ba.internal.appnameupper()} config file' + ':\n\n\nCheck the console' + ' (press ~ twice) for details.\n\nWould you like to quit and' + ' try to fix it by hand\nor overwrite it with defaults?\n\n' + '(high scores, player profiles, etc will be lost if you' + ' overwrite)' + ), + ) + ba.textwidget( + parent=self._root_widget, + position=(padding, 198), + size=(width - 2 * padding, 100 - 2 * padding), + h_align='center', + v_align='top', + scale=0.5, + text=self._config_file_path, + ) + quit_button = ba.buttonwidget( + parent=self._root_widget, + position=(35, 30), + size=(240, 54), + label='Quit and Edit', + on_activate_call=self._quit, + ) + ba.buttonwidget( + parent=self._root_widget, + position=(width - 370, 30), + size=(330, 54), + label='Overwrite with Defaults', + on_activate_call=self._defaults, + ) + ba.containerwidget( + edit=self._root_widget, + cancel_button=quit_button, + selected_child=quit_button, + ) def _quit(self) -> None: ba.timer(0.001, self._edit_and_quit, timetype=ba.TimeType.REAL) @@ -66,6 +78,7 @@ class ConfigErrorWindow(ba.Window): def _defaults(self) -> None: from ba.internal import commit_app_config + ba.containerwidget(edit=self._root_widget, transition='out_left') ba.playsound(ba.getsound('gunCocking')) ba.screenmessage('settings reset.', color=(1, 1, 0)) diff --git a/dist/ba_data/python/bastd/ui/confirm.py b/dist/ba_data/python/bastd/ui/confirm.py index 46bb90b..74e32dc 100644 --- a/dist/ba_data/python/bastd/ui/confirm.py +++ b/dist/ba_data/python/bastd/ui/confirm.py @@ -16,18 +16,20 @@ if TYPE_CHECKING: class ConfirmWindow: """Window for answering simple yes/no questions.""" - def __init__(self, - text: str | ba.Lstr = 'Are you sure?', - action: Callable[[], Any] | None = None, - width: float = 360.0, - height: float = 100.0, - cancel_button: bool = True, - cancel_is_selected: bool = False, - color: tuple[float, float, float] = (1, 1, 1), - text_scale: float = 1.0, - ok_text: str | ba.Lstr | None = None, - cancel_text: str | ba.Lstr | None = None, - origin_widget: ba.Widget | None = None): + def __init__( + self, + text: str | ba.Lstr = 'Are you sure?', + action: Callable[[], Any] | None = None, + width: float = 360.0, + height: float = 100.0, + cancel_button: bool = True, + cancel_is_selected: bool = False, + color: tuple[float, float, float] = (1, 1, 1), + text_scale: float = 1.0, + ok_text: str | ba.Lstr | None = None, + cancel_text: str | ba.Lstr | None = None, + origin_widget: ba.Widget | None = None, + ): # pylint: disable=too-many-locals if ok_text is None: ok_text = ba.Lstr(resource='okText') @@ -55,29 +57,39 @@ class ConfirmWindow: transition=transition, toolbar_visibility='menu_minimal_no_back', parent=ba.internal.get_special_widget('overlay_stack'), - scale=(2.1 if uiscale is ba.UIScale.SMALL else - 1.5 if uiscale is ba.UIScale.MEDIUM else 1.0), - scale_origin_stack_offset=scale_origin) + scale=( + 2.1 + if uiscale is ba.UIScale.SMALL + else 1.5 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + scale_origin_stack_offset=scale_origin, + ) - ba.textwidget(parent=self.root_widget, - position=(width * 0.5, height - 5 - (height - 75) * 0.5), - size=(0, 0), - h_align='center', - v_align='center', - text=text, - scale=text_scale, - color=color, - maxwidth=width * 0.9, - max_height=height - 75) + ba.textwidget( + parent=self.root_widget, + position=(width * 0.5, height - 5 - (height - 75) * 0.5), + size=(0, 0), + h_align='center', + v_align='center', + text=text, + scale=text_scale, + color=color, + maxwidth=width * 0.9, + max_height=height - 75, + ) cbtn: ba.Widget | None if cancel_button: - cbtn = btn = ba.buttonwidget(parent=self.root_widget, - autoselect=True, - position=(20, 20), - size=(150, 50), - label=cancel_text, - on_activate_call=self._cancel) + cbtn = btn = ba.buttonwidget( + parent=self.root_widget, + autoselect=True, + position=(20, 20), + size=(150, 50), + label=cancel_text, + on_activate_call=self._cancel, + ) ba.containerwidget(edit=self.root_widget, cancel_button=btn) ok_button_h = width - 175 else: @@ -86,37 +98,51 @@ class ConfirmWindow: # button ok_button_h = width * 0.5 - 75 cbtn = None - btn = ba.buttonwidget(parent=self.root_widget, - autoselect=True, - position=(ok_button_h, 20), - size=(150, 50), - label=ok_text, - on_activate_call=self._ok) + btn = ba.buttonwidget( + parent=self.root_widget, + autoselect=True, + position=(ok_button_h, 20), + size=(150, 50), + label=ok_text, + on_activate_call=self._ok, + ) # if they didn't want a cancel button, we still want to be able to hit # cancel/back/etc to dismiss the window if not cancel_button: - ba.containerwidget(edit=self.root_widget, - on_cancel_call=btn.activate) + ba.containerwidget( + edit=self.root_widget, on_cancel_call=btn.activate + ) - ba.containerwidget(edit=self.root_widget, - selected_child=(cbtn if cbtn is not None - and cancel_is_selected else btn), - start_button=btn) + ba.containerwidget( + edit=self.root_widget, + selected_child=( + cbtn if cbtn is not None and cancel_is_selected else btn + ), + start_button=btn, + ) def _cancel(self) -> None: ba.containerwidget( edit=self.root_widget, - transition=('out_right' if self._transition_out is None else - self._transition_out)) + transition=( + 'out_right' + if self._transition_out is None + else self._transition_out + ), + ) def _ok(self) -> None: if not self.root_widget: return ba.containerwidget( edit=self.root_widget, - transition=('out_left' if self._transition_out is None else - self._transition_out)) + transition=( + 'out_left' + if self._transition_out is None + else self._transition_out + ), + ) if self._action is not None: self._action() @@ -124,10 +150,12 @@ class ConfirmWindow: class QuitWindow: """Popup window to confirm quitting.""" - def __init__(self, - swish: bool = False, - back: bool = False, - origin_widget: ba.Widget | None = None): + def __init__( + self, + swish: bool = False, + back: bool = False, + origin_widget: ba.Widget | None = None, + ): ui = ba.app.ui app = ba.app self._back = back @@ -138,19 +166,22 @@ class QuitWindow: ui.quit_window = None if swish: ba.playsound(ba.getsound('swish')) - quit_resource = ('quitGameText' - if app.platform == 'mac' else 'exitGameText') - self._root_widget = ui.quit_window = (ConfirmWindow( - ba.Lstr(resource=quit_resource, - subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))]), + quit_resource = ( + 'quitGameText' if app.platform == 'mac' else 'exitGameText' + ) + self._root_widget = ui.quit_window = ConfirmWindow( + ba.Lstr( + resource=quit_resource, + subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))], + ), self._fade_and_quit, - origin_widget=origin_widget).root_widget) + origin_widget=origin_widget, + ).root_widget def _fade_and_quit(self) -> None: ba.internal.fade_screen( - False, - time=0.2, - endcall=lambda: ba.quit(soft=True, back=self._back)) + False, time=0.2, endcall=lambda: ba.quit(soft=True, back=self._back) + ) ba.internal.lock_all_input() # Unlock and fade back in shortly.. just in case something goes wrong diff --git a/dist/ba_data/python/bastd/ui/continues.py b/dist/ba_data/python/bastd/ui/continues.py index 85e9b7b..11bdeed 100644 --- a/dist/ba_data/python/bastd/ui/continues.py +++ b/dist/ba_data/python/bastd/ui/continues.py @@ -17,9 +17,13 @@ if TYPE_CHECKING: class ContinuesWindow(ba.Window): """A window to continue a game.""" - def __init__(self, activity: ba.Activity, cost: int, - continue_call: Callable[[], Any], cancel_call: Callable[[], - Any]): + def __init__( + self, + activity: ba.Activity, + cost: int, + continue_call: Callable[[], Any], + cancel_call: Callable[[], Any], + ): self._activity = weakref.ref(activity) self._cost = cost self._continue_call = continue_call @@ -29,88 +33,114 @@ class ContinuesWindow(ba.Window): self._height = 200 self._transitioning_out = False super().__init__( - ba.containerwidget(size=(self._width, self._height), - background=False, - toolbar_visibility='menu_currency', - transition='in_scale', - scale=1.5)) - txt = (ba.Lstr( - resource='continuePurchaseText').evaluate().split('${PRICE}')) + ba.containerwidget( + size=(self._width, self._height), + background=False, + toolbar_visibility='menu_currency', + transition='in_scale', + scale=1.5, + ) + ) + txt = ( + ba.Lstr(resource='continuePurchaseText') + .evaluate() + .split('${PRICE}') + ) t_left = txt[0] - t_left_width = ba.internal.get_string_width(t_left, - suppress_warning=True) + t_left_width = ba.internal.get_string_width( + t_left, suppress_warning=True + ) t_price = ba.charstr(ba.SpecialChar.TICKET) + str(self._cost) - t_price_width = ba.internal.get_string_width(t_price, - suppress_warning=True) + t_price_width = ba.internal.get_string_width( + t_price, suppress_warning=True + ) t_right = txt[-1] - t_right_width = ba.internal.get_string_width(t_right, - suppress_warning=True) + t_right_width = ba.internal.get_string_width( + t_right, suppress_warning=True + ) width_total_half = (t_left_width + t_price_width + t_right_width) * 0.5 - ba.textwidget(parent=self._root_widget, - text=t_left, - flatness=1.0, - shadow=1.0, - size=(0, 0), - h_align='left', - v_align='center', - position=(self._width * 0.5 - width_total_half, - self._height - 30)) - ba.textwidget(parent=self._root_widget, - text=t_price, - flatness=1.0, - shadow=1.0, - color=(0.2, 1.0, 0.2), - size=(0, 0), - position=(self._width * 0.5 - width_total_half + - t_left_width, self._height - 30), - h_align='left', - v_align='center') - ba.textwidget(parent=self._root_widget, - text=t_right, - flatness=1.0, - shadow=1.0, - size=(0, 0), - h_align='left', - v_align='center', - position=(self._width * 0.5 - width_total_half + - t_left_width + t_price_width + 5, - self._height - 30)) + ba.textwidget( + parent=self._root_widget, + text=t_left, + flatness=1.0, + shadow=1.0, + size=(0, 0), + h_align='left', + v_align='center', + position=(self._width * 0.5 - width_total_half, self._height - 30), + ) + ba.textwidget( + parent=self._root_widget, + text=t_price, + flatness=1.0, + shadow=1.0, + color=(0.2, 1.0, 0.2), + size=(0, 0), + position=( + self._width * 0.5 - width_total_half + t_left_width, + self._height - 30, + ), + h_align='left', + v_align='center', + ) + ba.textwidget( + parent=self._root_widget, + text=t_right, + flatness=1.0, + shadow=1.0, + size=(0, 0), + h_align='left', + v_align='center', + position=( + self._width * 0.5 + - width_total_half + + t_left_width + + t_price_width + + 5, + self._height - 30, + ), + ) self._tickets_text_base: str | None self._tickets_text: ba.Widget | None if not ba.app.ui.use_toolbars: self._tickets_text_base = ba.Lstr( resource='getTicketsWindow.youHaveShortText', - fallback_resource='getTicketsWindow.youHaveText').evaluate() + fallback_resource='getTicketsWindow.youHaveText', + ).evaluate() self._tickets_text = ba.textwidget( parent=self._root_widget, text='', flatness=1.0, color=(0.2, 1.0, 0.2), shadow=1.0, - position=(self._width * 0.5 + width_total_half, - self._height - 50), + position=( + self._width * 0.5 + width_total_half, + self._height - 50, + ), size=(0, 0), scale=0.35, h_align='right', - v_align='center') + v_align='center', + ) else: self._tickets_text_base = None self._tickets_text = None - self._counter_text = ba.textwidget(parent=self._root_widget, - text=str(self._count), - color=(0.7, 0.7, 0.7), - scale=1.2, - size=(0, 0), - big=True, - position=(self._width * 0.5, - self._height - 80), - flatness=1.0, - shadow=1.0, - h_align='center', - v_align='center') + self._counter_text = ba.textwidget( + parent=self._root_widget, + text=str(self._count), + color=(0.7, 0.7, 0.7), + scale=1.2, + size=(0, 0), + big=True, + position=(self._width * 0.5, self._height - 80), + flatness=1.0, + shadow=1.0, + h_align='center', + v_align='center', + ) self._cancel_button = ba.buttonwidget( parent=self._root_widget, position=(30, 30), @@ -118,24 +148,27 @@ class ContinuesWindow(ba.Window): label=ba.Lstr(resource='endText', fallback_resource='cancelText'), autoselect=True, enable_sound=False, - on_activate_call=self._on_cancel_press) + on_activate_call=self._on_cancel_press, + ) self._continue_button = ba.buttonwidget( parent=self._root_widget, label=ba.Lstr(resource='continueText'), autoselect=True, position=(self._width - 130, 30), size=(120, 50), - on_activate_call=self._on_continue_press) - ba.containerwidget(edit=self._root_widget, - cancel_button=self._cancel_button, - start_button=self._continue_button, - selected_child=self._cancel_button) + on_activate_call=self._on_continue_press, + ) + ba.containerwidget( + edit=self._root_widget, + cancel_button=self._cancel_button, + start_button=self._continue_button, + selected_child=self._cancel_button, + ) self._counting_down = True - self._countdown_timer = ba.Timer(1.0, - ba.WeakCall(self._tick), - repeat=True, - timetype=ba.TimeType.REAL) + self._countdown_timer = ba.Timer( + 1.0, ba.WeakCall(self._tick), repeat=True, timetype=ba.TimeType.REAL + ) # If there is foreground activity, suspend it. ba.app.pause() @@ -153,15 +186,17 @@ class ContinuesWindow(ba.Window): return if ba.internal.get_v1_account_state() == 'signed_in': - sval = (ba.charstr(ba.SpecialChar.TICKET) + - str(ba.internal.get_v1_account_ticket_count())) + sval = ba.charstr(ba.SpecialChar.TICKET) + str( + ba.internal.get_v1_account_ticket_count() + ) else: sval = '?' if self._tickets_text is not None: assert self._tickets_text_base is not None - ba.textwidget(edit=self._tickets_text, - text=self._tickets_text_base.replace( - '${COUNT}', sval)) + ba.textwidget( + edit=self._tickets_text, + text=self._tickets_text_base.replace('${COUNT}', sval), + ) if self._counting_down: self._count -= 1 @@ -187,8 +222,9 @@ class ContinuesWindow(ba.Window): else: # If somehow we got signed out... if ba.internal.get_v1_account_state() != 'signed_in': - ba.screenmessage(ba.Lstr(resource='notSignedInText'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr(resource='notSignedInText'), color=(1, 0, 0) + ) ba.playsound(ba.getsound('error')) return @@ -204,8 +240,9 @@ class ContinuesWindow(ba.Window): if not self._transitioning_out: ba.playsound(ba.getsound('swish')) self._transitioning_out = True - ba.containerwidget(edit=self._root_widget, - transition='out_scale') + ba.containerwidget( + edit=self._root_widget, transition='out_scale' + ) self._continue_call() def _on_cancel(self) -> None: diff --git a/dist/ba_data/python/bastd/ui/coop/browser.py b/dist/ba_data/python/bastd/ui/coop/browser.py index b6d978d..f96e0a5 100644 --- a/dist/ba_data/python/bastd/ui/coop/browser.py +++ b/dist/ba_data/python/bastd/ui/coop/browser.py @@ -25,20 +25,36 @@ class CoopBrowserWindow(ba.Window): def _update_corner_button_positions(self) -> None: uiscale = ba.app.ui.uiscale - offs = (-55 if uiscale is ba.UIScale.SMALL - and ba.internal.is_party_icon_visible() else 0) + offs = ( + -55 + if uiscale is ba.UIScale.SMALL + and ba.internal.is_party_icon_visible() + else 0 + ) if self._league_rank_button is not None: self._league_rank_button.set_position( - (self._width - 282 + offs - self._x_inset, self._height - 85 - - (4 if uiscale is ba.UIScale.SMALL else 0))) + ( + self._width - 282 + offs - self._x_inset, + self._height + - 85 + - (4 if uiscale is ba.UIScale.SMALL else 0), + ) + ) if self._store_button is not None: self._store_button.set_position( - (self._width - 170 + offs - self._x_inset, self._height - 85 - - (4 if uiscale is ba.UIScale.SMALL else 0))) + ( + self._width - 170 + offs - self._x_inset, + self._height + - 85 + - (4 if uiscale is ba.UIScale.SMALL else 0), + ) + ) - def __init__(self, - transition: str | None = 'in_right', - origin_widget: ba.Widget | None = None): + def __init__( + self, + transition: str | None = 'in_right', + origin_widget: ba.Widget | None = None, + ): # pylint: disable=too-many-statements # pylint: disable=cyclic-import import threading @@ -55,12 +71,14 @@ class CoopBrowserWindow(ba.Window): # Quick note to players that tourneys won't work in ballistica # core builds. (need to split the word so it won't get subbed out) if 'ballistica' + 'core' == ba.internal.appname(): - ba.timer(1.0, - lambda: ba.screenmessage( - ba.Lstr(resource='noTournamentsInTestBuildText'), - color=(1, 1, 0), - ), - timetype=ba.TimeType.REAL) + ba.timer( + 1.0, + lambda: ba.screenmessage( + ba.Lstr(resource='noTournamentsInTestBuildText'), + color=(1, 1, 0), + ), + timetype=ba.TimeType.REAL, + ) # If they provided an origin-widget, scale up from that. scale_origin: tuple[float, float] | None @@ -85,8 +103,13 @@ class CoopBrowserWindow(ba.Window): uiscale = ba.app.ui.uiscale self._width = 1320 if uiscale is ba.UIScale.SMALL else 1120 self._x_inset = x_inset = 100 if uiscale is ba.UIScale.SMALL else 0 - self._height = (657 if uiscale is ba.UIScale.SMALL else - 730 if uiscale is ba.UIScale.MEDIUM else 800) + self._height = ( + 657 + if uiscale is ba.UIScale.SMALL + else 730 + if uiscale is ba.UIScale.MEDIUM + else 800 + ) app.ui.set_main_menu_location('Coop Select') self._r = 'coopSelectWindow' top_extra = 20 if uiscale is ba.UIScale.SMALL else 0 @@ -94,30 +117,49 @@ class CoopBrowserWindow(ba.Window): self._tourney_data_up_to_date = False self._campaign_difficulty = ba.internal.get_v1_account_misc_val( - 'campaignDifficulty', 'easy') + 'campaignDifficulty', 'easy' + ) - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height + top_extra), - toolbar_visibility='menu_full', - scale_origin_stack_offset=scale_origin, - stack_offset=((0, -15) if uiscale is ba.UIScale.SMALL else ( - 0, 0) if uiscale is ba.UIScale.MEDIUM else (0, 0)), - transition=transition, - scale=(1.2 if uiscale is ba.UIScale.SMALL else - 0.8 if uiscale is ba.UIScale.MEDIUM else 0.75))) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height + top_extra), + toolbar_visibility='menu_full', + scale_origin_stack_offset=scale_origin, + stack_offset=( + (0, -15) + if uiscale is ba.UIScale.SMALL + else (0, 0) + if uiscale is ba.UIScale.MEDIUM + else (0, 0) + ), + transition=transition, + scale=( + 1.2 + if uiscale is ba.UIScale.SMALL + else 0.8 + if uiscale is ba.UIScale.MEDIUM + else 0.75 + ), + ) + ) if app.ui.use_toolbars and uiscale is ba.UIScale.SMALL: self._back_button = None else: self._back_button = ba.buttonwidget( parent=self._root_widget, - position=(75 + x_inset, self._height - 87 - - (4 if uiscale is ba.UIScale.SMALL else 0)), + position=( + 75 + x_inset, + self._height + - 87 + - (4 if uiscale is ba.UIScale.SMALL else 0), + ), size=(120, 60), scale=1.2, autoselect=True, label=ba.Lstr(resource='backText'), - button_type='back') + button_type='back', + ) self._league_rank_button: LeagueRankButton | None self._store_button: StoreButton | None @@ -127,19 +169,28 @@ class CoopBrowserWindow(ba.Window): if not app.ui.use_toolbars: prb = self._league_rank_button = LeagueRankButton( parent=self._root_widget, - position=(self._width - (282 + x_inset), self._height - 85 - - (4 if uiscale is ba.UIScale.SMALL else 0)), + position=( + self._width - (282 + x_inset), + self._height + - 85 + - (4 if uiscale is ba.UIScale.SMALL else 0), + ), size=(100, 60), color=(0.4, 0.4, 0.9), textcolor=(0.9, 0.9, 2.0), scale=1.05, - on_activate_call=ba.WeakCall(self._switch_to_league_rankings)) + on_activate_call=ba.WeakCall(self._switch_to_league_rankings), + ) self._league_rank_button_widget = prb.get_button() sbtn = self._store_button = StoreButton( parent=self._root_widget, - position=(self._width - (170 + x_inset), self._height - 85 - - (4 if uiscale is ba.UIScale.SMALL else 0)), + position=( + self._width - (170 + x_inset), + self._height + - 85 + - (4 if uiscale is ba.UIScale.SMALL else 0), + ), size=(100, 60), color=(0.6, 0.4, 0.7), show_tickets=True, @@ -147,12 +198,17 @@ class CoopBrowserWindow(ba.Window): sale_scale=0.85, textcolor=(0.9, 0.7, 1.0), scale=1.05, - on_activate_call=ba.WeakCall(self._switch_to_score, None)) + on_activate_call=ba.WeakCall(self._switch_to_score, None), + ) self._store_button_widget = sbtn.get_button() - ba.widget(edit=self._back_button, - right_widget=self._league_rank_button_widget) - ba.widget(edit=self._league_rank_button_widget, - left_widget=self._back_button) + ba.widget( + edit=self._back_button, + right_widget=self._league_rank_button_widget, + ) + ba.widget( + edit=self._league_rank_button_widget, + left_widget=self._back_button, + ) else: self._league_rank_button = None self._store_button = None @@ -166,32 +222,40 @@ class CoopBrowserWindow(ba.Window): 1.0, ba.WeakCall(self._update_corner_button_positions), repeat=True, - timetype=ba.TimeType.REAL) + timetype=ba.TimeType.REAL, + ) self._last_tournament_query_time: float | None = None self._last_tournament_query_response_time: float | None = None self._doing_tournament_query = False - self._selected_campaign_level = (cfg.get( - 'Selected Coop Campaign Level', None)) - self._selected_custom_level = (cfg.get('Selected Coop Custom Level', - None)) + self._selected_campaign_level = cfg.get( + 'Selected Coop Campaign Level', None + ) + self._selected_custom_level = cfg.get( + 'Selected Coop Custom Level', None + ) # Don't want initial construction affecting our last-selected. self._do_selection_callbacks = False v = self._height - 95 txt = ba.textwidget( parent=self._root_widget, - position=(self._width * 0.5, - v + 40 - (0 if uiscale is ba.UIScale.SMALL else 0)), + position=( + self._width * 0.5, + v + 40 - (0 if uiscale is ba.UIScale.SMALL else 0), + ), size=(0, 0), - text=ba.Lstr(resource='playModes.singlePlayerCoopText', - fallback_resource='playModes.coopText'), + text=ba.Lstr( + resource='playModes.singlePlayerCoopText', + fallback_resource='playModes.coopText', + ), h_align='center', color=app.ui.title_color, scale=1.5, maxwidth=500, - v_align='center') + v_align='center', + ) if app.ui.use_toolbars and uiscale is ba.UIScale.SMALL: ba.textwidget(edit=txt, text='') @@ -201,9 +265,15 @@ class CoopBrowserWindow(ba.Window): edit=self._back_button, button_type='backSmall', size=(60, 50), - position=(75 + x_inset, self._height - 87 - - (4 if uiscale is ba.UIScale.SMALL else 0) + 6), - label=ba.charstr(ba.SpecialChar.BACK)) + position=( + 75 + x_inset, + self._height + - 87 + - (4 if uiscale is ba.UIScale.SMALL else 0) + + 6, + ), + label=ba.charstr(ba.SpecialChar.BACK), + ) self._selected_row = cfg.get('Selected Coop Row', None) @@ -214,9 +284,9 @@ class CoopBrowserWindow(ba.Window): self.a_outline_model = ba.getmodel('achievementOutline') self._scroll_width = self._width - (130 + 2 * x_inset) - self._scroll_height = (self._height - - (190 if uiscale is ba.UIScale.SMALL - and app.ui.use_toolbars else 160)) + self._scroll_height = self._height - ( + 190 if uiscale is ba.UIScale.SMALL and app.ui.use_toolbars else 160 + ) self._subcontainerwidth = 800.0 self._subcontainerheight = 1400.0 @@ -224,13 +294,15 @@ class CoopBrowserWindow(ba.Window): self._scrollwidget = ba.scrollwidget( parent=self._root_widget, highlight=False, - position=(65 + x_inset, 120) if uiscale is ba.UIScale.SMALL - and app.ui.use_toolbars else (65 + x_inset, 70), + position=(65 + x_inset, 120) + if uiscale is ba.UIScale.SMALL and app.ui.use_toolbars + else (65 + x_inset, 70), size=(self._scroll_width, self._scroll_height), simple_culling_v=10.0, claims_left_right=True, claims_tab=True, - selection_loops_to_parent=True) + selection_loops_to_parent=True, + ) self._subcontainer: ba.Widget | None = None # Take note of our account state; we'll refresh later if this changes. @@ -250,11 +322,15 @@ class CoopBrowserWindow(ba.Window): # If we've got a cached tournament list for our account and info for # each one of those tournaments, go ahead and display it as a # starting point. - if (app.accounts_v1.account_tournament_list is not None - and app.accounts_v1.account_tournament_list[0] - == ba.internal.get_v1_account_state_num() and all( - t_id in app.accounts_v1.tournament_info - for t_id in app.accounts_v1.account_tournament_list[1])): + if ( + app.accounts_v1.account_tournament_list is not None + and app.accounts_v1.account_tournament_list[0] + == ba.internal.get_v1_account_state_num() + and all( + t_id in app.accounts_v1.tournament_info + for t_id in app.accounts_v1.account_tournament_list[1] + ) + ): tourney_data = [ app.accounts_v1.tournament_info[t_id] for t_id in app.accounts_v1.account_tournament_list[1] @@ -262,10 +338,12 @@ class CoopBrowserWindow(ba.Window): self._update_for_data(tourney_data) # This will pull new data periodically, update timers, etc. - self._update_timer = ba.Timer(1.0, - ba.WeakCall(self._update), - timetype=ba.TimeType.REAL, - repeat=True) + self._update_timer = ba.Timer( + 1.0, + ba.WeakCall(self._update), + timetype=ba.TimeType.REAL, + repeat=True, + ) self._update() # noinspection PyUnresolvedReferences @@ -294,9 +372,12 @@ class CoopBrowserWindow(ba.Window): # If its been a while since we got a tournament update, consider the # data invalid (prevents us from joining tournaments if our internet # connection goes down for a while). - if (self._last_tournament_query_response_time is None - or ba.time(ba.TimeType.REAL) - - self._last_tournament_query_response_time > 60.0 * 2): + if ( + self._last_tournament_query_response_time is None + or ba.time(ba.TimeType.REAL) + - self._last_tournament_query_response_time + > 60.0 * 2 + ): self._tourney_data_up_to_date = False # If our account state has changed, do a full request. @@ -318,17 +399,15 @@ class CoopBrowserWindow(ba.Window): # Send off a new tournament query if its been long enough or whatnot. if not self._doing_tournament_query and ( - self._last_tournament_query_time is None - or cur_time - self._last_tournament_query_time > 30.0 - or self._fg_state != ba.app.fg_state): + self._last_tournament_query_time is None + or cur_time - self._last_tournament_query_time > 30.0 + or self._fg_state != ba.app.fg_state + ): self._fg_state = ba.app.fg_state self._last_tournament_query_time = cur_time self._doing_tournament_query = True ba.internal.tournament_query( - args={ - 'source': 'coop window refresh', - 'numScores': 1 - }, + args={'source': 'coop window refresh', 'numScores': 1}, callback=ba.WeakCall(self._on_tournament_query_response), ) @@ -339,18 +418,28 @@ class CoopBrowserWindow(ba.Window): if tbtn.time_remaining_value_text is not None: ba.textwidget( edit=tbtn.time_remaining_value_text, - text=ba.timestring(tbtn.time_remaining, - centi=False, - suppress_format_warning=True) if - (tbtn.has_time_remaining - and self._tourney_data_up_to_date) else '-') + text=ba.timestring( + tbtn.time_remaining, + centi=False, + suppress_format_warning=True, + ) + if ( + tbtn.has_time_remaining + and self._tourney_data_up_to_date + ) + else '-', + ) # Also adjust the ad icon visibility. if tbtn.allow_ads and ba.internal.has_video_ads(): - ba.imagewidget(edit=tbtn.entry_fee_ad_image, - opacity=1.0 if ads_enabled else 0.25) - ba.textwidget(edit=tbtn.entry_fee_text_remaining, - color=(0.6, 0.6, 0.6, 1 if ads_enabled else 0.2)) + ba.imagewidget( + edit=tbtn.entry_fee_ad_image, + opacity=1.0 if ads_enabled else 0.25, + ) + ba.textwidget( + edit=tbtn.entry_fee_text_remaining, + color=(0.6, 0.6, 0.6, 1 if ads_enabled else 0.2), + ) self._update_hard_mode_lock_image() @@ -358,7 +447,8 @@ class CoopBrowserWindow(ba.Window): try: ba.imagewidget( edit=self._hard_button_lock_image, - opacity=0.0 if ba.app.accounts_v1.have_pro_options() else 1.0) + opacity=0.0 if ba.app.accounts_v1.have_pro_options() else 1.0, + ) except Exception: ba.print_exception('Error updating campaign lock.') @@ -366,11 +456,10 @@ class CoopBrowserWindow(ba.Window): # If the number of tournaments or challenges in the data differs from # our current arrangement, refresh with the new number. - if ((data is None and self._tournament_button_count != 0) - or (data is not None and - (len(data) != self._tournament_button_count))): - self._tournament_button_count = (len(data) - if data is not None else 0) + if (data is None and self._tournament_button_count != 0) or ( + data is not None and (len(data) != self._tournament_button_count) + ): + self._tournament_button_count = len(data) if data is not None else 0 ba.app.config['Tournament Rows'] = self._tournament_button_count self._refresh() @@ -379,13 +468,15 @@ class CoopBrowserWindow(ba.Window): assert data is not None tbtn.update_for_data(data[i]) - def _on_tournament_query_response(self, - data: dict[str, Any] | None) -> None: + def _on_tournament_query_response( + self, data: dict[str, Any] | None + ) -> None: accounts = ba.app.accounts_v1 if data is not None: tournament_data = data['t'] # This used to be the whole payload. self._last_tournament_query_response_time = ba.time( - ba.TimeType.REAL) + ba.TimeType.REAL + ) else: tournament_data = None @@ -397,7 +488,8 @@ class CoopBrowserWindow(ba.Window): # Also cache the current tourney list/order for this account. accounts.account_tournament_list = ( ba.internal.get_v1_account_state_num(), - [e['tournamentID'] for e in tournament_data]) + [e['tournamentID'] for e in tournament_data], + ) self._doing_tournament_query = False self._update_for_data(tournament_data) @@ -405,9 +497,12 @@ class CoopBrowserWindow(ba.Window): def _set_campaign_difficulty(self, difficulty: str) -> None: # pylint: disable=cyclic-import from bastd.ui.purchase import PurchaseWindow + if difficulty != self._campaign_difficulty: - if (difficulty == 'hard' - and not ba.app.accounts_v1.have_pro_options()): + if ( + difficulty == 'hard' + and not ba.app.accounts_v1.have_pro_options() + ): PurchaseWindow(items=['pro']) return ba.playsound(ba.getsound('gunCocking')) @@ -415,11 +510,13 @@ class CoopBrowserWindow(ba.Window): print('ERROR: invalid campaign difficulty:', difficulty) difficulty = 'easy' self._campaign_difficulty = difficulty - ba.internal.add_transaction({ - 'type': 'SET_MISC_VAL', - 'name': 'campaignDifficulty', - 'value': difficulty - }) + ba.internal.add_transaction( + { + 'type': 'SET_MISC_VAL', + 'name': 'campaignDifficulty', + 'value': difficulty, + } + ) self._refresh_campaign_row() else: ba.playsound(ba.getsound('click01')) @@ -429,6 +526,7 @@ class CoopBrowserWindow(ba.Window): # pylint: disable=cyclic-import from ba.internal import getcampaign from bastd.ui.coop.gamebutton import GameButton + parent_widget = self._campaign_sub_container # Clear out anything in the parent widget already. @@ -455,14 +553,19 @@ class CoopBrowserWindow(ba.Window): on_activate_call=ba.Call(self._set_campaign_difficulty, 'easy'), on_select_call=ba.Call(self.sel_change, 'campaign', 'easyButton'), color=sel_color - if self._campaign_difficulty == 'easy' else un_sel_color, + if self._campaign_difficulty == 'easy' + else un_sel_color, textcolor=sel_textcolor - if self._campaign_difficulty == 'easy' else un_sel_textcolor) + if self._campaign_difficulty == 'easy' + else un_sel_textcolor, + ) ba.widget(edit=self._easy_button, show_buffer_left=100) if self._selected_campaign_level == 'easyButton': - ba.containerwidget(edit=parent_widget, - selected_child=self._easy_button, - visible_child=self._easy_button) + ba.containerwidget( + edit=parent_widget, + selected_child=self._easy_button, + visible_child=self._easy_button, + ) lock_tex = ba.gettexture('lock') self._hard_button = ba.buttonwidget( @@ -476,21 +579,27 @@ class CoopBrowserWindow(ba.Window): on_activate_call=ba.Call(self._set_campaign_difficulty, 'hard'), on_select_call=ba.Call(self.sel_change, 'campaign', 'hardButton'), color=sel_color_hard - if self._campaign_difficulty == 'hard' else un_sel_color, + if self._campaign_difficulty == 'hard' + else un_sel_color, textcolor=sel_textcolor - if self._campaign_difficulty == 'hard' else un_sel_textcolor) + if self._campaign_difficulty == 'hard' + else un_sel_textcolor, + ) self._hard_button_lock_image = ba.imagewidget( parent=parent_widget, size=(30, 30), draw_controller=self._hard_button, position=(h + 30 - 10, v2 + 32 + 70 - 35), - texture=lock_tex) + texture=lock_tex, + ) self._update_hard_mode_lock_image() ba.widget(edit=self._hard_button, show_buffer_left=100) if self._selected_campaign_level == 'hardButton': - ba.containerwidget(edit=parent_widget, - selected_child=self._hard_button, - visible_child=self._hard_button) + ba.containerwidget( + edit=parent_widget, + selected_child=self._hard_button, + visible_child=self._hard_button, + ) ba.widget(edit=self._hard_button, down_widget=next_widget_down) h_spacing = 200 @@ -515,10 +624,12 @@ class CoopBrowserWindow(ba.Window): self._selected_campaign_level = items[0] h = 150 for i in items: - is_last_sel = (i == self._selected_campaign_level) + is_last_sel = i == self._selected_campaign_level campaign_buttons.append( - GameButton(self, parent_widget, i, h, v2, is_last_sel, - 'campaign').get_button()) + GameButton( + self, parent_widget, i, h, v2, is_last_sel, 'campaign' + ).get_button() + ) h += h_spacing ba.widget(edit=campaign_buttons[0], left_widget=self._easy_button) @@ -526,9 +637,11 @@ class CoopBrowserWindow(ba.Window): if self._back_button is not None: ba.widget(edit=self._easy_button, up_widget=self._back_button) for btn in campaign_buttons: - ba.widget(edit=btn, - up_widget=self._back_button, - down_widget=next_widget_down) + ba.widget( + edit=btn, + up_widget=self._back_button, + down_widget=next_widget_down, + ) # Update our existing percent-complete text. campaign = getcampaign(campaignname) @@ -541,20 +654,27 @@ class CoopBrowserWindow(ba.Window): self._campaign_percent_text = ba.textwidget( edit=self._campaign_percent_text, - text=ba.Lstr(value='${C} (${P})', - subs=[('${C}', - ba.Lstr(resource=self._r + '.campaignText')), - ('${P}', p_str)])) + text=ba.Lstr( + value='${C} (${P})', + subs=[ + ('${C}', ba.Lstr(resource=self._r + '.campaignText')), + ('${P}', p_str), + ], + ), + ) def _on_tournament_info_press(self) -> None: # pylint: disable=cyclic-import from bastd.ui.confirm import ConfirmWindow + txt = ba.Lstr(resource=self._r + '.tournamentInfoText') - ConfirmWindow(txt, - cancel_button=False, - width=550, - height=260, - origin_widget=self._tournament_info_button) + ConfirmWindow( + txt, + cancel_button=False, + width=550, + height=260, + origin_widget=self._tournament_info_button, + ) def _refresh(self) -> None: # pylint: disable=too-many-statements @@ -570,7 +690,8 @@ class CoopBrowserWindow(ba.Window): tourney_row_height = 200 self._subcontainerheight = ( - 620 + self._tournament_button_count * tourney_row_height) + 620 + self._tournament_button_count * tourney_row_height + ) self._subcontainer = ba.containerwidget( parent=self._scrollwidget, @@ -578,13 +699,16 @@ class CoopBrowserWindow(ba.Window): background=False, claims_left_right=True, claims_tab=True, - selection_loops_to_parent=True) + selection_loops_to_parent=True, + ) - ba.containerwidget(edit=self._root_widget, - selected_child=self._scrollwidget) + ba.containerwidget( + edit=self._root_widget, selected_child=self._scrollwidget + ) if self._back_button is not None: - ba.containerwidget(edit=self._root_widget, - cancel_button=self._back_button) + ba.containerwidget( + edit=self._root_widget, cancel_button=self._back_button + ) w_parent = self._subcontainer h_base = 6 @@ -599,7 +723,8 @@ class CoopBrowserWindow(ba.Window): h_align='left', v_align='center', color=ba.app.ui.title_color, - scale=1.1) + scale=1.1, + ) row_v_show_buffer = 100 v -= 198 @@ -612,21 +737,23 @@ class CoopBrowserWindow(ba.Window): highlight=False, border_opacity=0.0, color=(0.45, 0.4, 0.5), - on_select_call=lambda: self._on_row_selected('campaign')) + on_select_call=lambda: self._on_row_selected('campaign'), + ) self._campaign_h_scroll = h_scroll - ba.widget(edit=h_scroll, - show_buffer_top=row_v_show_buffer, - show_buffer_bottom=row_v_show_buffer, - autoselect=True) + ba.widget( + edit=h_scroll, + show_buffer_top=row_v_show_buffer, + show_buffer_bottom=row_v_show_buffer, + autoselect=True, + ) if self._selected_row == 'campaign': - ba.containerwidget(edit=w_parent, - selected_child=h_scroll, - visible_child=h_scroll) + ba.containerwidget( + edit=w_parent, selected_child=h_scroll, visible_child=h_scroll + ) ba.containerwidget(edit=h_scroll, claims_left_right=True) - self._campaign_sub_container = ba.containerwidget(parent=h_scroll, - size=(180 + 200 * 10, - 200), - background=False) + self._campaign_sub_container = ba.containerwidget( + parent=h_scroll, size=(180 + 200 * 10, 200), background=False + ) # Tournaments @@ -634,17 +761,20 @@ class CoopBrowserWindow(ba.Window): v -= 53 # FIXME shouldn't use hard-coded strings here. - txt = ba.Lstr(resource='tournamentsText', - fallback_resource='tournamentText').evaluate() + txt = ba.Lstr( + resource='tournamentsText', fallback_resource='tournamentText' + ).evaluate() t_width = ba.internal.get_string_width(txt, suppress_warning=True) - ba.textwidget(parent=w_parent, - position=(h_base + 27, v + 30), - size=(0, 0), - text=txt, - h_align='left', - v_align='center', - color=ba.app.ui.title_color, - scale=1.1) + ba.textwidget( + parent=w_parent, + position=(h_base + 27, v + 30), + size=(0, 0), + text=txt, + h_align='left', + v_align='center', + color=ba.app.ui.title_color, + scale=1.1, + ) self._tournament_info_button = ba.buttonwidget( parent=w_parent, label='?', @@ -656,10 +786,13 @@ class CoopBrowserWindow(ba.Window): textcolor=(0.7, 0.6, 0.75), autoselect=True, up_widget=self._campaign_h_scroll, - on_activate_call=self._on_tournament_info_press) - ba.widget(edit=self._tournament_info_button, - left_widget=self._tournament_info_button, - right_widget=self._tournament_info_button) + on_activate_call=self._on_tournament_info_press, + ) + ba.widget( + edit=self._tournament_info_button, + left_widget=self._tournament_info_button, + right_widget=self._tournament_info_button, + ) # Say 'unavailable' if there are zero tournaments, and if we're not # signed in add that as well (that's probably why we see @@ -669,16 +802,21 @@ class CoopBrowserWindow(ba.Window): if ba.internal.get_v1_account_state() != 'signed_in': unavailable_text = ba.Lstr( value='${A} (${B})', - subs=[('${A}', unavailable_text), - ('${B}', ba.Lstr(resource='notSignedInText'))]) - ba.textwidget(parent=w_parent, - position=(h_base + 47, v), - size=(0, 0), - text=unavailable_text, - h_align='left', - v_align='center', - color=ba.app.ui.title_color, - scale=0.9) + subs=[ + ('${A}', unavailable_text), + ('${B}', ba.Lstr(resource='notSignedInText')), + ], + ) + ba.textwidget( + parent=w_parent, + position=(h_base + 47, v), + size=(0, 0), + text=unavailable_text, + h_align='left', + v_align='center', + color=ba.app.ui.title_color, + scale=0.9, + ) v -= 40 v -= 198 @@ -692,44 +830,57 @@ class CoopBrowserWindow(ba.Window): highlight=False, border_opacity=0.0, color=(0.45, 0.4, 0.5), - on_select_call=ba.Call(self._on_row_selected, - 'tournament' + str(i + 1))) - ba.widget(edit=h_scroll, - show_buffer_top=row_v_show_buffer, - show_buffer_bottom=row_v_show_buffer, - autoselect=True) + on_select_call=ba.Call( + self._on_row_selected, 'tournament' + str(i + 1) + ), + ) + ba.widget( + edit=h_scroll, + show_buffer_top=row_v_show_buffer, + show_buffer_bottom=row_v_show_buffer, + autoselect=True, + ) if self._selected_row == 'tournament' + str(i + 1): - ba.containerwidget(edit=w_parent, - selected_child=h_scroll, - visible_child=h_scroll) + ba.containerwidget( + edit=w_parent, + selected_child=h_scroll, + visible_child=h_scroll, + ) ba.containerwidget(edit=h_scroll, claims_left_right=True) - sc2 = ba.containerwidget(parent=h_scroll, - size=(self._scroll_width - 24, 200), - background=False) + sc2 = ba.containerwidget( + parent=h_scroll, + size=(self._scroll_width - 24, 200), + background=False, + ) h = 0 v2 = -2 is_last_sel = True self._tournament_buttons.append( - TournamentButton(sc2, - h, - v2, - is_last_sel, - on_pressed=ba.WeakCall( - self.run_tournament))) + TournamentButton( + sc2, + h, + v2, + is_last_sel, + on_pressed=ba.WeakCall(self.run_tournament), + ) + ) v -= 200 # Custom Games. (called 'Practice' in UI these days). v -= 50 - ba.textwidget(parent=w_parent, - position=(h_base + 27, v + 30 + 198), - size=(0, 0), - text=ba.Lstr( - resource='practiceText', - fallback_resource='coopSelectWindow.customText'), - h_align='left', - v_align='center', - color=ba.app.ui.title_color, - scale=1.1) + ba.textwidget( + parent=w_parent, + position=(h_base + 27, v + 30 + 198), + size=(0, 0), + text=ba.Lstr( + resource='practiceText', + fallback_resource='coopSelectWindow.customText', + ), + h_align='left', + v_align='center', + color=ba.app.ui.title_color, + scale=1.1, + ) items = [ 'Challenges:Infinite Onslaught', @@ -743,8 +894,8 @@ class CoopBrowserWindow(ba.Window): # Show easter-egg-hunt either if its easter or we own it. if ba.internal.get_v1_account_misc_read_val( - 'easter', - False) or ba.internal.get_purchased('games.easter_egg_hunt'): + 'easter', False + ) or ba.internal.get_purchased('games.easter_egg_hunt'): items = [ 'Challenges:Easter Egg Hunt', 'Challenges:Pro Easter Egg Hunt', @@ -761,28 +912,33 @@ class CoopBrowserWindow(ba.Window): highlight=False, border_opacity=0.0, color=(0.45, 0.4, 0.5), - on_select_call=ba.Call(self._on_row_selected, 'custom')) - ba.widget(edit=h_scroll, - show_buffer_top=row_v_show_buffer, - show_buffer_bottom=1.5 * row_v_show_buffer, - autoselect=True) + on_select_call=ba.Call(self._on_row_selected, 'custom'), + ) + ba.widget( + edit=h_scroll, + show_buffer_top=row_v_show_buffer, + show_buffer_bottom=1.5 * row_v_show_buffer, + autoselect=True, + ) if self._selected_row == 'custom': - ba.containerwidget(edit=w_parent, - selected_child=h_scroll, - visible_child=h_scroll) + ba.containerwidget( + edit=w_parent, selected_child=h_scroll, visible_child=h_scroll + ) ba.containerwidget(edit=h_scroll, claims_left_right=True) - sc2 = ba.containerwidget(parent=h_scroll, - size=(max(self._scroll_width - 24, - 30 + 200 * len(items)), 200), - background=False) + sc2 = ba.containerwidget( + parent=h_scroll, + size=(max(self._scroll_width - 24, 30 + 200 * len(items)), 200), + background=False, + ) h_spacing = 200 self._custom_buttons: list[GameButton] = [] h = 0 v2 = -2 for item in items: - is_last_sel = (item == self._selected_custom_level) + is_last_sel = item == self._selected_custom_level self._custom_buttons.append( - GameButton(self, sc2, item, h, v2, is_last_sel, 'custom')) + GameButton(self, sc2, item, h, v2, is_last_sel, 'custom') + ) h += h_spacing # We can't fill in our campaign row until tourney buttons are in place. @@ -793,33 +949,44 @@ class CoopBrowserWindow(ba.Window): ba.widget( edit=tbutton.button, up_widget=self._tournament_info_button - if i == 0 else self._tournament_buttons[i - 1].button, + if i == 0 + else self._tournament_buttons[i - 1].button, down_widget=self._tournament_buttons[(i + 1)].button - if i + 1 < len(self._tournament_buttons) else custom_h_scroll) + if i + 1 < len(self._tournament_buttons) + else custom_h_scroll, + ) ba.widget( edit=tbutton.more_scores_button, - down_widget=self._tournament_buttons[( - i + 1)].current_leader_name_text - if i + 1 < len(self._tournament_buttons) else custom_h_scroll) - ba.widget(edit=tbutton.current_leader_name_text, - up_widget=self._tournament_info_button if i == 0 else - self._tournament_buttons[i - 1].more_scores_button) + down_widget=self._tournament_buttons[ + (i + 1) + ].current_leader_name_text + if i + 1 < len(self._tournament_buttons) + else custom_h_scroll, + ) + ba.widget( + edit=tbutton.current_leader_name_text, + up_widget=self._tournament_info_button + if i == 0 + else self._tournament_buttons[i - 1].more_scores_button, + ) for btn in self._custom_buttons: try: ba.widget( edit=btn.get_button(), - up_widget=tournament_h_scroll if self._tournament_buttons - else self._tournament_info_button) + up_widget=tournament_h_scroll + if self._tournament_buttons + else self._tournament_info_button, + ) except Exception: ba.print_exception('Error wiring up custom buttons.') if self._back_button is not None: - ba.buttonwidget(edit=self._back_button, - on_activate_call=self._back) + ba.buttonwidget(edit=self._back_button, on_activate_call=self._back) else: - ba.containerwidget(edit=self._root_widget, - on_cancel_call=self._back) + ba.containerwidget( + edit=self._root_widget, on_cancel_call=self._back + ) # There's probably several 'onSelected' callbacks pushed onto the # event queue.. we need to push ours too so we're enabled *after* them. @@ -837,6 +1004,7 @@ class CoopBrowserWindow(ba.Window): # pylint: disable=cyclic-import from bastd.ui.account import show_sign_in_prompt from bastd.ui.league.rankwindow import LeagueRankWindow + if ba.internal.get_v1_account_state() != 'signed_in': show_sign_in_prompt() return @@ -844,16 +1012,19 @@ class CoopBrowserWindow(ba.Window): ba.containerwidget(edit=self._root_widget, transition='out_left') assert self._league_rank_button is not None ba.app.ui.set_main_menu_window( - LeagueRankWindow(origin_widget=self._league_rank_button.get_button( - )).get_root_widget()) + LeagueRankWindow( + origin_widget=self._league_rank_button.get_button() + ).get_root_widget() + ) def _switch_to_score( self, show_tab: StoreBrowserWindow.TabID - | None = StoreBrowserWindow.TabID.EXTRAS + | None = StoreBrowserWindow.TabID.EXTRAS, ) -> None: # pylint: disable=cyclic-import from bastd.ui.account import show_sign_in_prompt + if ba.internal.get_v1_account_state() != 'signed_in': show_sign_in_prompt() return @@ -864,7 +1035,9 @@ class CoopBrowserWindow(ba.Window): StoreBrowserWindow( origin_widget=self._store_button.get_button(), show_tab=show_tab, - back_location='CoopBrowserWindow').get_root_widget()) + back_location='CoopBrowserWindow', + ).get_root_widget() + ) def is_tourney_data_up_to_date(self) -> bool: """Return whether our tourney data is up to date.""" @@ -877,21 +1050,31 @@ class CoopBrowserWindow(ba.Window): from bastd.ui.confirm import ConfirmWindow from bastd.ui.purchase import PurchaseWindow from bastd.ui.account import show_sign_in_prompt + args: dict[str, Any] = {} if game == 'Easy:The Last Stand': - ConfirmWindow(ba.Lstr(resource='difficultyHardUnlockOnlyText', - fallback_resource='difficultyHardOnlyText'), - cancel_button=False, - width=460, - height=130) + ConfirmWindow( + ba.Lstr( + resource='difficultyHardUnlockOnlyText', + fallback_resource='difficultyHardOnlyText', + ), + cancel_button=False, + width=460, + height=130, + ) return # Infinite onslaught/runaround require pro; bring up a store link # if need be. - if game in ('Challenges:Infinite Runaround', - 'Challenges:Infinite Onslaught' - ) and not ba.app.accounts_v1.have_pro(): + if ( + game + in ( + 'Challenges:Infinite Runaround', + 'Challenges:Infinite Onslaught', + ) + and not ba.app.accounts_v1.have_pro() + ): if ba.internal.get_v1_account_state() != 'signed_in': show_sign_in_prompt() else: @@ -902,8 +1085,8 @@ class CoopBrowserWindow(ba.Window): if game in ['Challenges:Meteor Shower']: required_purchase = 'games.meteor_shower' elif game in [ - 'Challenges:Target Practice', - 'Challenges:Target Practice B', + 'Challenges:Target Practice', + 'Challenges:Target Practice B', ]: required_purchase = 'games.target_practice' elif game in ['Challenges:Ninja Fight']: @@ -911,15 +1094,16 @@ class CoopBrowserWindow(ba.Window): elif game in ['Challenges:Pro Ninja Fight']: required_purchase = 'games.ninja_fight' elif game in [ - 'Challenges:Easter Egg Hunt', - 'Challenges:Pro Easter Egg Hunt', + 'Challenges:Easter Egg Hunt', + 'Challenges:Pro Easter Egg Hunt', ]: required_purchase = 'games.easter_egg_hunt' else: required_purchase = None - if (required_purchase is not None - and not ba.internal.get_purchased(required_purchase)): + if required_purchase is not None and not ba.internal.get_purchased( + required_purchase + ): if ba.internal.get_v1_account_state() != 'signed_in': show_sign_in_prompt() else: @@ -943,20 +1127,23 @@ class CoopBrowserWindow(ba.Window): if ba.internal.workspaces_in_use(): ba.screenmessage( ba.Lstr(resource='tournamentsDisabledWorkspaceText'), - color=(1, 0, 0)) + color=(1, 0, 0), + ) ba.playsound(ba.getsound('error')) return if not self._tourney_data_up_to_date: - ba.screenmessage(ba.Lstr(resource='tournamentCheckingStateText'), - color=(1, 1, 0)) + ba.screenmessage( + ba.Lstr(resource='tournamentCheckingStateText'), color=(1, 1, 0) + ) ba.playsound(ba.getsound('error')) return if tournament_button.tournament_id is None: ba.screenmessage( ba.Lstr(resource='internal.unavailableNoConnectionText'), - color=(1, 0, 0)) + color=(1, 0, 0), + ) ba.playsound(ba.getsound('error')) return @@ -964,19 +1151,27 @@ class CoopBrowserWindow(ba.Window): ba.screenmessage( ba.Lstr( resource='league.tournamentLeagueText', - subs=[('${NAME}', - ba.Lstr( - translate=('leagueNames', - tournament_button.required_league))) - ]), + subs=[ + ( + '${NAME}', + ba.Lstr( + translate=( + 'leagueNames', + tournament_button.required_league, + ) + ), + ) + ], + ), color=(1, 0, 0), ) ba.playsound(ba.getsound('error')) return if tournament_button.time_remaining <= 0: - ba.screenmessage(ba.Lstr(resource='tournamentEndedText'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr(resource='tournamentEndedText'), color=(1, 0, 0) + ) ba.playsound(ba.getsound('error')) return @@ -985,7 +1180,8 @@ class CoopBrowserWindow(ba.Window): assert tournament_button.tournament_id is not None TournamentEntryWindow( tournament_id=tournament_button.tournament_id, - position=tournament_button.button.get_screen_space_center()) + position=tournament_button.button.get_screen_space_center(), + ) def _back(self) -> None: # pylint: disable=cyclic-import @@ -993,10 +1189,12 @@ class CoopBrowserWindow(ba.Window): # If something is selected, store it. self._save_state() - ba.containerwidget(edit=self._root_widget, - transition=self._transition_out) + ba.containerwidget( + edit=self._root_widget, transition=self._transition_out + ) ba.app.ui.set_main_menu_window( - PlayWindow(transition='in_left').get_root_widget()) + PlayWindow(transition='in_left').get_root_widget() + ) def _save_state(self) -> None: cfg = ba.app.config @@ -1023,8 +1221,9 @@ class CoopBrowserWindow(ba.Window): def _restore_state(self) -> None: try: - sel_name = ba.app.ui.window_states.get(type(self), - {}).get('sel_name') + sel_name = ba.app.ui.window_states.get(type(self), {}).get( + 'sel_name' + ) if sel_name == 'Back': sel = self._back_button elif sel_name == 'Scroll': diff --git a/dist/ba_data/python/bastd/ui/coop/gamebutton.py b/dist/ba_data/python/bastd/ui/coop/gamebutton.py index 6324783..4182416 100644 --- a/dist/ba_data/python/bastd/ui/coop/gamebutton.py +++ b/dist/ba_data/python/bastd/ui/coop/gamebutton.py @@ -16,11 +16,20 @@ if TYPE_CHECKING: class GameButton: """Button for entering co-op games.""" - def __init__(self, window: CoopBrowserWindow, parent: ba.Widget, game: str, - x: float, y: float, select: bool, row: str): + def __init__( + self, + window: CoopBrowserWindow, + parent: ba.Widget, + game: str, + x: float, + y: float, + select: bool, + row: str, + ): # pylint: disable=too-many-statements # pylint: disable=too-many-locals from ba.internal import getcampaign + self._game = game sclx = 195.0 scly = 195.0 @@ -57,16 +66,19 @@ class GameButton: on_activate_call=ba.Call(window.run_game, game), button_type='square', autoselect=True, - on_select_call=ba.Call(window.sel_change, row, game)) - ba.widget(edit=btn, - show_buffer_bottom=50, - show_buffer_top=50, - show_buffer_left=400, - show_buffer_right=200) + on_select_call=ba.Call(window.sel_change, row, game), + ) + ba.widget( + edit=btn, + show_buffer_bottom=50, + show_buffer_top=50, + show_buffer_left=400, + show_buffer_right=200, + ) if select: - ba.containerwidget(edit=parent, - selected_child=btn, - visible_child=btn) + ba.containerwidget( + edit=parent, selected_child=btn, visible_child=btn + ) image_width = sclx * 0.85 * 0.75 self._preview_widget = ba.imagewidget( parent=parent, @@ -76,21 +88,23 @@ class GameButton: model_transparent=window.lsbt, model_opaque=window.lsbo, texture=campaign.getlevel(levelname).get_preview_texture(), - mask_texture=ba.gettexture('mapPreviewMask')) + mask_texture=ba.gettexture('mapPreviewMask'), + ) translated = campaign.getlevel(levelname).displayname self._achievements = ba.app.ach.achievements_for_coop_level(game) - self._name_widget = ba.textwidget(parent=parent, - draw_controller=btn, - position=(x + 20 + sclx * 0.5, - y + scly - 27), - size=(0, 0), - h_align='center', - text=translated, - v_align='center', - maxwidth=sclx * 0.76, - scale=0.85) + self._name_widget = ba.textwidget( + parent=parent, + draw_controller=btn, + position=(x + 20 + sclx * 0.5, y + scly - 27), + size=(0, 0), + h_align='center', + text=translated, + v_align='center', + maxwidth=sclx * 0.76, + scale=0.85, + ) xscl = x + (67 if self._achievements else 50) yscl = y + scly - (137 if self._achievements else 157) @@ -98,21 +112,25 @@ class GameButton: self._star_widgets: list[ba.Widget] = [] for _i in range(stars): - imw = ba.imagewidget(parent=parent, - draw_controller=btn, - position=(xscl, yscl), - size=(starscale, starscale), - texture=window.star_tex) + imw = ba.imagewidget( + parent=parent, + draw_controller=btn, + position=(xscl, yscl), + size=(starscale, starscale), + texture=window.star_tex, + ) self._star_widgets.append(imw) xscl += starscale for _i in range(3 - stars): - ba.imagewidget(parent=parent, - draw_controller=btn, - position=(xscl, yscl), - size=(starscale, starscale), - color=(0, 0, 0), - texture=window.star_tex, - opacity=0.3) + ba.imagewidget( + parent=parent, + draw_controller=btn, + position=(xscl, yscl), + size=(starscale, starscale), + color=(0, 0, 0), + texture=window.star_tex, + opacity=0.3, + ) xscl += starscale xach = x + 69 @@ -127,33 +145,40 @@ class GameButton: position=(xach, yach), size=(a_scale, a_scale), color=tuple(ach.get_icon_color(a_complete)[:3]) - if a_complete else (1.2, 1.2, 1.2), - texture=ach.get_icon_texture(a_complete)) - imw2 = ba.imagewidget(parent=parent, - draw_controller=btn, - position=(xach, yach), - size=(a_scale, a_scale), - color=(2, 1.4, 0.4), - texture=window.a_outline_tex, - model_transparent=window.a_outline_model) + if a_complete + else (1.2, 1.2, 1.2), + texture=ach.get_icon_texture(a_complete), + ) + imw2 = ba.imagewidget( + parent=parent, + draw_controller=btn, + position=(xach, yach), + size=(a_scale, a_scale), + color=(2, 1.4, 0.4), + texture=window.a_outline_tex, + model_transparent=window.a_outline_model, + ) self._achievement_widgets.append((imw, imw2)) # if a_complete: xach += a_scale * 1.2 # if not unlocked: - self._lock_widget = ba.imagewidget(parent=parent, - draw_controller=btn, - position=(x - 8 + sclx * 0.5, - y + scly * 0.5 - 20), - size=(60, 60), - opacity=0.0, - texture=ba.gettexture('lock')) + self._lock_widget = ba.imagewidget( + parent=parent, + draw_controller=btn, + position=(x - 8 + sclx * 0.5, y + scly * 0.5 - 20), + size=(60, 60), + opacity=0.0, + texture=ba.gettexture('lock'), + ) # give a quasi-random update increment to spread the load.. - self._update_timer = ba.Timer(0.001 * (900 + random.randrange(200)), - ba.WeakCall(self._update), - repeat=True, - timetype=ba.TimeType.REAL) + self._update_timer = ba.Timer( + 0.001 * (900 + random.randrange(200)), + ba.WeakCall(self._update), + repeat=True, + timetype=ba.TimeType.REAL, + ) self._update() def get_button(self) -> ba.Widget: @@ -195,46 +220,84 @@ class GameButton: unlocked = False # Hard-code games we haven't unlocked. - if ((game in ('Challenges:Infinite Runaround', - 'Challenges:Infinite Onslaught') - and not ba.app.accounts_v1.have_pro()) - or (game in ('Challenges:Meteor Shower', ) - and not ba.internal.get_purchased('games.meteor_shower')) - or (game in ('Challenges:Target Practice', - 'Challenges:Target Practice B') - and not ba.internal.get_purchased('games.target_practice')) - or (game in ('Challenges:Ninja Fight', ) - and not ba.internal.get_purchased('games.ninja_fight')) - or (game in ('Challenges:Pro Ninja Fight', ) - and not ba.internal.get_purchased('games.ninja_fight')) or - (game in ('Challenges:Easter Egg Hunt', - 'Challenges:Pro Easter Egg Hunt') - and not ba.internal.get_purchased('games.easter_egg_hunt'))): + if ( + ( + game + in ( + 'Challenges:Infinite Runaround', + 'Challenges:Infinite Onslaught', + ) + and not ba.app.accounts_v1.have_pro() + ) + or ( + game in ('Challenges:Meteor Shower',) + and not ba.internal.get_purchased('games.meteor_shower') + ) + or ( + game + in ( + 'Challenges:Target Practice', + 'Challenges:Target Practice B', + ) + and not ba.internal.get_purchased('games.target_practice') + ) + or ( + game in ('Challenges:Ninja Fight',) + and not ba.internal.get_purchased('games.ninja_fight') + ) + or ( + game in ('Challenges:Pro Ninja Fight',) + and not ba.internal.get_purchased('games.ninja_fight') + ) + or ( + game + in ( + 'Challenges:Easter Egg Hunt', + 'Challenges:Pro Easter Egg Hunt', + ) + and not ba.internal.get_purchased('games.easter_egg_hunt') + ) + ): unlocked = False # Let's tint levels a slightly different color when easy mode # is selected. - unlocked_color = (0.85, 0.95, - 0.5) if game.startswith('Easy:') else (0.5, 0.7, 0.2) + unlocked_color = ( + (0.85, 0.95, 0.5) if game.startswith('Easy:') else (0.5, 0.7, 0.2) + ) - ba.buttonwidget(edit=self._button, - color=unlocked_color if unlocked else (0.5, 0.5, 0.5)) + ba.buttonwidget( + edit=self._button, + color=unlocked_color if unlocked else (0.5, 0.5, 0.5), + ) - ba.imagewidget(edit=self._lock_widget, - opacity=0.0 if unlocked else 1.0) - ba.imagewidget(edit=self._preview_widget, - opacity=1.0 if unlocked else 0.3) - ba.textwidget(edit=self._name_widget, - color=(0.8, 1.0, 0.8, 1.0) if unlocked else - (0.7, 0.7, 0.7, 0.7)) + ba.imagewidget(edit=self._lock_widget, opacity=0.0 if unlocked else 1.0) + ba.imagewidget( + edit=self._preview_widget, opacity=1.0 if unlocked else 0.3 + ) + ba.textwidget( + edit=self._name_widget, + color=(0.8, 1.0, 0.8, 1.0) if unlocked else (0.7, 0.7, 0.7, 0.7), + ) for widget in self._star_widgets: - ba.imagewidget(edit=widget, - opacity=1.0 if unlocked else 0.3, - color=(2.2, 1.2, 0.3) if unlocked else (1, 1, 1)) + ba.imagewidget( + edit=widget, + opacity=1.0 if unlocked else 0.3, + color=(2.2, 1.2, 0.3) if unlocked else (1, 1, 1), + ) for i, ach in enumerate(self._achievements): a_complete = ach.complete - ba.imagewidget(edit=self._achievement_widgets[i][0], - opacity=1.0 if (a_complete and unlocked) else 0.3) - ba.imagewidget(edit=self._achievement_widgets[i][1], - opacity=(1.0 if (a_complete and unlocked) else - 0.2 if a_complete else 0.0)) + ba.imagewidget( + edit=self._achievement_widgets[i][0], + opacity=1.0 if (a_complete and unlocked) else 0.3, + ) + ba.imagewidget( + edit=self._achievement_widgets[i][1], + opacity=( + 1.0 + if (a_complete and unlocked) + else 0.2 + if a_complete + else 0.0 + ), + ) diff --git a/dist/ba_data/python/bastd/ui/coop/level.py b/dist/ba_data/python/bastd/ui/coop/level.py index c244469..fd19541 100644 --- a/dist/ba_data/python/bastd/ui/coop/level.py +++ b/dist/ba_data/python/bastd/ui/coop/level.py @@ -15,44 +15,63 @@ class CoopLevelLockedWindow(ba.Window): height = 250.0 lock_tex = ba.gettexture('lock') uiscale = ba.app.ui.uiscale - super().__init__(root_widget=ba.containerwidget( - size=(width, height), - transition='in_right', - scale=(1.7 if uiscale is ba.UIScale.SMALL else - 1.3 if uiscale is ba.UIScale.MEDIUM else 1.0))) - ba.textwidget(parent=self._root_widget, - position=(150 - 20, height * 0.63), - size=(0, 0), - h_align='left', - v_align='center', - text=ba.Lstr(resource='levelIsLockedText', - subs=[('${LEVEL}', name)]), - maxwidth=400, - color=(1, 0.8, 0.3, 1), - scale=1.1) - ba.textwidget(parent=self._root_widget, - position=(150 - 20, height * 0.48), - size=(0, 0), - h_align='left', - v_align='center', - text=ba.Lstr(resource='levelMustBeCompletedFirstText', - subs=[('${LEVEL}', dep_name)]), - maxwidth=400, - color=ba.app.ui.infotextcolor, - scale=0.8) - ba.imagewidget(parent=self._root_widget, - position=(56 - 20, height * 0.39), - size=(80, 80), - texture=lock_tex, - opacity=1.0) - btn = ba.buttonwidget(parent=self._root_widget, - position=((width - 140) / 2, 30), - size=(140, 50), - label=ba.Lstr(resource='okText'), - on_activate_call=self._ok) - ba.containerwidget(edit=self._root_widget, - selected_child=btn, - start_button=btn) + super().__init__( + root_widget=ba.containerwidget( + size=(width, height), + transition='in_right', + scale=( + 1.7 + if uiscale is ba.UIScale.SMALL + else 1.3 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + ) + ) + ba.textwidget( + parent=self._root_widget, + position=(150 - 20, height * 0.63), + size=(0, 0), + h_align='left', + v_align='center', + text=ba.Lstr( + resource='levelIsLockedText', subs=[('${LEVEL}', name)] + ), + maxwidth=400, + color=(1, 0.8, 0.3, 1), + scale=1.1, + ) + ba.textwidget( + parent=self._root_widget, + position=(150 - 20, height * 0.48), + size=(0, 0), + h_align='left', + v_align='center', + text=ba.Lstr( + resource='levelMustBeCompletedFirstText', + subs=[('${LEVEL}', dep_name)], + ), + maxwidth=400, + color=ba.app.ui.infotextcolor, + scale=0.8, + ) + ba.imagewidget( + parent=self._root_widget, + position=(56 - 20, height * 0.39), + size=(80, 80), + texture=lock_tex, + opacity=1.0, + ) + btn = ba.buttonwidget( + parent=self._root_widget, + position=((width - 140) / 2, 30), + size=(140, 50), + label=ba.Lstr(resource='okText'), + on_activate_call=self._ok, + ) + ba.containerwidget( + edit=self._root_widget, selected_child=btn, start_button=btn + ) ba.playsound(ba.getsound('error')) def _ok(self) -> None: diff --git a/dist/ba_data/python/bastd/ui/coop/tournamentbutton.py b/dist/ba_data/python/bastd/ui/coop/tournamentbutton.py index 041b6c5..9f43978 100644 --- a/dist/ba_data/python/bastd/ui/coop/tournamentbutton.py +++ b/dist/ba_data/python/bastd/ui/coop/tournamentbutton.py @@ -17,8 +17,14 @@ if TYPE_CHECKING: class TournamentButton: """Button showing a tournament in coop window.""" - def __init__(self, parent: ba.Widget, x: float, y: float, select: bool, - on_pressed: Callable[[TournamentButton], None]) -> None: + def __init__( + self, + parent: ba.Widget, + x: float, + y: float, + select: bool, + on_pressed: Callable[[TournamentButton], None], + ) -> None: self._r = 'coopSelectWindow' sclx = 300 scly = 195.0 @@ -39,16 +45,19 @@ class TournamentButton: button_type='square', autoselect=True, # on_activate_call=lambda: self.run(None, tournament_button=data) - on_activate_call=ba.WeakCall(self._pressed)) - ba.widget(edit=btn, - show_buffer_bottom=50, - show_buffer_top=50, - show_buffer_left=400, - show_buffer_right=200) + on_activate_call=ba.WeakCall(self._pressed), + ) + ba.widget( + edit=btn, + show_buffer_bottom=50, + show_buffer_top=50, + show_buffer_left=400, + show_buffer_right=200, + ) if select: - ba.containerwidget(edit=parent, - selected_child=btn, - visible_child=btn) + ba.containerwidget( + edit=parent, selected_child=btn, visible_child=btn + ) image_width = sclx * 0.85 * 0.75 self.image = ba.imagewidget( @@ -60,103 +69,113 @@ class TournamentButton: model_opaque=self.lsbo, texture=ba.gettexture('black'), opacity=0.2, - mask_texture=ba.gettexture('mapPreviewMask')) + mask_texture=ba.gettexture('mapPreviewMask'), + ) self.lock_image = ba.imagewidget( parent=parent, draw_controller=btn, - position=(x + 21 + sclx * 0.5 - image_width * 0.25, - y + scly - 150), + position=(x + 21 + sclx * 0.5 - image_width * 0.25, y + scly - 150), size=(image_width * 0.5, image_width * 0.5), texture=ba.gettexture('lock'), - opacity=0.0) + opacity=0.0, + ) - self.button_text = ba.textwidget(parent=parent, - draw_controller=btn, - position=(x + 20 + sclx * 0.5, - y + scly - 35), - size=(0, 0), - h_align='center', - text='-', - v_align='center', - maxwidth=sclx * 0.76, - scale=0.85, - color=(0.8, 1.0, 0.8, 1.0)) + self.button_text = ba.textwidget( + parent=parent, + draw_controller=btn, + position=(x + 20 + sclx * 0.5, y + scly - 35), + size=(0, 0), + h_align='center', + text='-', + v_align='center', + maxwidth=sclx * 0.76, + scale=0.85, + color=(0.8, 1.0, 0.8, 1.0), + ) header_color = (0.43, 0.4, 0.5, 1) value_color = (0.6, 0.6, 0.6, 1) x_offs = 0 - ba.textwidget(parent=parent, - draw_controller=btn, - position=(x + 360, y + scly - 20), - size=(0, 0), - h_align='center', - text=ba.Lstr(resource=self._r + '.entryFeeText'), - v_align='center', - maxwidth=100, - scale=0.9, - color=header_color, - flatness=1.0) + ba.textwidget( + parent=parent, + draw_controller=btn, + position=(x + 360, y + scly - 20), + size=(0, 0), + h_align='center', + text=ba.Lstr(resource=self._r + '.entryFeeText'), + v_align='center', + maxwidth=100, + scale=0.9, + color=header_color, + flatness=1.0, + ) - self.entry_fee_text_top = ba.textwidget(parent=parent, - draw_controller=btn, - position=(x + 360, - y + scly - 60), - size=(0, 0), - h_align='center', - text='-', - v_align='center', - maxwidth=60, - scale=1.3, - color=value_color, - flatness=1.0) - self.entry_fee_text_or = ba.textwidget(parent=parent, - draw_controller=btn, - position=(x + 360, - y + scly - 90), - size=(0, 0), - h_align='center', - text='', - v_align='center', - maxwidth=60, - scale=0.5, - color=value_color, - flatness=1.0) - self.entry_fee_text_remaining = ba.textwidget(parent=parent, - draw_controller=btn, - position=(x + 360, - y + scly - 90), - size=(0, 0), - h_align='center', - text='', - v_align='center', - maxwidth=60, - scale=0.5, - color=value_color, - flatness=1.0) + self.entry_fee_text_top = ba.textwidget( + parent=parent, + draw_controller=btn, + position=(x + 360, y + scly - 60), + size=(0, 0), + h_align='center', + text='-', + v_align='center', + maxwidth=60, + scale=1.3, + color=value_color, + flatness=1.0, + ) + self.entry_fee_text_or = ba.textwidget( + parent=parent, + draw_controller=btn, + position=(x + 360, y + scly - 90), + size=(0, 0), + h_align='center', + text='', + v_align='center', + maxwidth=60, + scale=0.5, + color=value_color, + flatness=1.0, + ) + self.entry_fee_text_remaining = ba.textwidget( + parent=parent, + draw_controller=btn, + position=(x + 360, y + scly - 90), + size=(0, 0), + h_align='center', + text='', + v_align='center', + maxwidth=60, + scale=0.5, + color=value_color, + flatness=1.0, + ) - self.entry_fee_ad_image = ba.imagewidget(parent=parent, - size=(40, 40), - draw_controller=btn, - position=(x + 360 - 20, - y + scly - 140), - opacity=0.0, - texture=ba.gettexture('tv')) + self.entry_fee_ad_image = ba.imagewidget( + parent=parent, + size=(40, 40), + draw_controller=btn, + position=(x + 360 - 20, y + scly - 140), + opacity=0.0, + texture=ba.gettexture('tv'), + ) x_offs += 50 - ba.textwidget(parent=parent, - draw_controller=btn, - position=(x + 447 + x_offs, y + scly - 20), - size=(0, 0), - h_align='center', - text=ba.Lstr(resource=self._r + '.prizesText'), - v_align='center', - maxwidth=130, - scale=0.9, - color=header_color, - flatness=1.0) + ba.textwidget( + parent=parent, + draw_controller=btn, + position=(x + 447 + x_offs, y + scly - 20), + size=(0, 0), + h_align='center', + text=ba.Lstr(resource=self._r + '.prizesText'), + v_align='center', + maxwidth=130, + scale=0.9, + color=header_color, + flatness=1.0, + ) self.button_x = x self.button_y = y @@ -176,7 +195,8 @@ class TournamentButton: text='-', scale=0.8, color=header_color, - flatness=1.0) + flatness=1.0, + ) self.prize_value_1_text = ba.textwidget( parent=parent, draw_controller=btn, @@ -188,7 +208,8 @@ class TournamentButton: maxwidth=100, scale=prize_value_scale, color=value_color, - flatness=1.0) + flatness=1.0, + ) self.prize_range_2_text = ba.textwidget( parent=parent, @@ -200,7 +221,8 @@ class TournamentButton: maxwidth=50, scale=0.8, color=header_color, - flatness=1.0) + flatness=1.0, + ) self.prize_value_2_text = ba.textwidget( parent=parent, draw_controller=btn, @@ -212,7 +234,8 @@ class TournamentButton: maxwidth=100, scale=prize_value_scale, color=value_color, - flatness=1.0) + flatness=1.0, + ) self.prize_range_3_text = ba.textwidget( parent=parent, @@ -224,7 +247,8 @@ class TournamentButton: maxwidth=50, scale=0.8, color=header_color, - flatness=1.0) + flatness=1.0, + ) self.prize_value_3_text = ba.textwidget( parent=parent, draw_controller=btn, @@ -236,24 +260,29 @@ class TournamentButton: maxwidth=100, scale=prize_value_scale, color=value_color, - flatness=1.0) + flatness=1.0, + ) - ba.textwidget(parent=parent, - draw_controller=btn, - position=(x + 620 + x_offs, y + scly - 20), - size=(0, 0), - h_align='center', - text=ba.Lstr(resource=self._r + '.currentBestText'), - v_align='center', - maxwidth=180, - scale=0.9, - color=header_color, - flatness=1.0) + ba.textwidget( + parent=parent, + draw_controller=btn, + position=(x + 620 + x_offs, y + scly - 20), + size=(0, 0), + h_align='center', + text=ba.Lstr(resource=self._r + '.currentBestText'), + v_align='center', + maxwidth=180, + scale=0.9, + color=header_color, + flatness=1.0, + ) self.current_leader_name_text = ba.textwidget( parent=parent, draw_controller=btn, - position=(x + 620 + x_offs - (170 / 1.4) * 0.5, - y + scly - 60 - 40 * 0.5), + position=( + x + 620 + x_offs - (170 / 1.4) * 0.5, + y + scly - 60 - 40 * 0.5, + ), selectable=True, click_activate=True, autoselect=True, @@ -265,7 +294,8 @@ class TournamentButton: maxwidth=170, scale=1.4, color=value_color, - flatness=1.0) + flatness=1.0, + ) self.current_leader_score_text = ba.textwidget( parent=parent, draw_controller=btn, @@ -277,7 +307,8 @@ class TournamentButton: maxwidth=170, scale=1.8, color=value_color, - flatness=1.0) + flatness=1.0, + ) self.more_scores_button = ba.buttonwidget( parent=parent, @@ -289,21 +320,26 @@ class TournamentButton: autoselect=True, up_widget=self.current_leader_name_text, text_scale=0.6, - on_activate_call=ba.WeakCall(self._show_scores)) - ba.widget(edit=self.current_leader_name_text, - down_widget=self.more_scores_button) + on_activate_call=ba.WeakCall(self._show_scores), + ) + ba.widget( + edit=self.current_leader_name_text, + down_widget=self.more_scores_button, + ) - ba.textwidget(parent=parent, - draw_controller=btn, - position=(x + 820 + x_offs, y + scly - 20), - size=(0, 0), - h_align='center', - text=ba.Lstr(resource=self._r + '.timeRemainingText'), - v_align='center', - maxwidth=180, - scale=0.9, - color=header_color, - flatness=1.0) + ba.textwidget( + parent=parent, + draw_controller=btn, + position=(x + 820 + x_offs, y + scly - 20), + size=(0, 0), + h_align='center', + text=ba.Lstr(resource=self._r + '.timeRemainingText'), + v_align='center', + maxwidth=180, + scale=0.9, + color=header_color, + flatness=1.0, + ) self.time_remaining_value_text = ba.textwidget( parent=parent, draw_controller=btn, @@ -315,7 +351,8 @@ class TournamentButton: maxwidth=180, scale=2.0, color=value_color, - flatness=1.0) + flatness=1.0, + ) self.time_remaining_out_of_text = ba.textwidget( parent=parent, draw_controller=btn, @@ -327,7 +364,8 @@ class TournamentButton: maxwidth=120, scale=0.72, color=(0.4, 0.4, 0.5), - flatness=1.0) + flatness=1.0, + ) def _pressed(self) -> None: self.on_pressed(self) @@ -335,23 +373,29 @@ class TournamentButton: def _show_leader(self) -> None: # pylint: disable=cyclic-import from bastd.ui.account.viewer import AccountViewerWindow + tournament_id = self.tournament_id # FIXME: This assumes a single player entry in leader; should expand # this to work with multiple. - if tournament_id is None or self.leader is None or len( - self.leader[2]) != 1: + if ( + tournament_id is None + or self.leader is None + or len(self.leader[2]) != 1 + ): ba.playsound(ba.getsound('error')) return ba.playsound(ba.getsound('swish')) AccountViewerWindow( account_id=self.leader[2][0].get('a', None), profile_id=self.leader[2][0].get('p', None), - position=self.current_leader_name_text.get_screen_space_center()) + position=self.current_leader_name_text.get_screen_space_center(), + ) def _show_scores(self) -> None: # pylint: disable=cyclic-import from bastd.ui.tournamentscores import TournamentScoresWindow + tournament_id = self.tournament_id if tournament_id is None: ba.playsound(ba.getsound('error')) @@ -359,7 +403,8 @@ class TournamentButton: TournamentScoresWindow( tournament_id=tournament_id, - position=self.more_scores_button.get_screen_space_center()) + position=self.more_scores_button.get_screen_space_center(), + ) def update_for_data(self, entry: dict[str, Any]) -> None: """Update for new incoming data.""" @@ -367,22 +412,34 @@ class TournamentButton: # pylint: disable=too-many-locals # pylint: disable=too-many-branches from ba.internal import getcampaign, get_tournament_prize_strings - prize_y_offs = (34 if 'prizeRange3' in entry else - 20 if 'prizeRange2' in entry else 12) + + prize_y_offs = ( + 34 + if 'prizeRange3' in entry + else 20 + if 'prizeRange2' in entry + else 12 + ) x_offs = 90 # This seems to be a false alarm. # pylint: disable=unbalanced-tuple-unpacking - pr1, pv1, pr2, pv2, pr3, pv3 = (get_tournament_prize_strings(entry)) + pr1, pv1, pr2, pv2, pr3, pv3 = get_tournament_prize_strings(entry) # pylint: enable=unbalanced-tuple-unpacking enabled = 'requiredLeague' not in entry - ba.buttonwidget(edit=self.button, - color=(0.5, 0.7, 0.2) if enabled else (0.5, 0.5, 0.5)) + ba.buttonwidget( + edit=self.button, + color=(0.5, 0.7, 0.2) if enabled else (0.5, 0.5, 0.5), + ) ba.imagewidget(edit=self.lock_image, opacity=0.0 if enabled else 1.0) - ba.textwidget(edit=self.prize_range_1_text, - text='-' if pr1 == '' else pr1, - position=(self.button_x + 365 + x_offs, self.button_y + - self.button_scale_y - 93 + prize_y_offs)) + ba.textwidget( + edit=self.prize_range_1_text, + text='-' if pr1 == '' else pr1, + position=( + self.button_x + 365 + x_offs, + self.button_y + self.button_scale_y - 93 + prize_y_offs, + ), + ) # We want to draw values containing tickets a bit smaller # (scratch that; we now draw medals a bit bigger). @@ -390,94 +447,144 @@ class TournamentButton: prize_value_scale_large = 1.0 prize_value_scale_small = 1.0 - ba.textwidget(edit=self.prize_value_1_text, - text='-' if pv1 == '' else pv1, - scale=prize_value_scale_large - if ticket_char not in pv1 else prize_value_scale_small, - position=(self.button_x + 380 + x_offs, self.button_y + - self.button_scale_y - 93 + prize_y_offs)) + ba.textwidget( + edit=self.prize_value_1_text, + text='-' if pv1 == '' else pv1, + scale=prize_value_scale_large + if ticket_char not in pv1 + else prize_value_scale_small, + position=( + self.button_x + 380 + x_offs, + self.button_y + self.button_scale_y - 93 + prize_y_offs, + ), + ) - ba.textwidget(edit=self.prize_range_2_text, - text=pr2, - position=(self.button_x + 365 + x_offs, self.button_y + - self.button_scale_y - 93 - 45 + prize_y_offs)) - ba.textwidget(edit=self.prize_value_2_text, - text=pv2, - scale=prize_value_scale_large - if ticket_char not in pv2 else prize_value_scale_small, - position=(self.button_x + 380 + x_offs, self.button_y + - self.button_scale_y - 93 - 45 + prize_y_offs)) + ba.textwidget( + edit=self.prize_range_2_text, + text=pr2, + position=( + self.button_x + 365 + x_offs, + self.button_y + self.button_scale_y - 93 - 45 + prize_y_offs, + ), + ) + ba.textwidget( + edit=self.prize_value_2_text, + text=pv2, + scale=prize_value_scale_large + if ticket_char not in pv2 + else prize_value_scale_small, + position=( + self.button_x + 380 + x_offs, + self.button_y + self.button_scale_y - 93 - 45 + prize_y_offs, + ), + ) - ba.textwidget(edit=self.prize_range_3_text, - text=pr3, - position=(self.button_x + 365 + x_offs, self.button_y + - self.button_scale_y - 93 - 90 + prize_y_offs)) - ba.textwidget(edit=self.prize_value_3_text, - text=pv3, - scale=prize_value_scale_large - if ticket_char not in pv3 else prize_value_scale_small, - position=(self.button_x + 380 + x_offs, self.button_y + - self.button_scale_y - 93 - 90 + prize_y_offs)) + ba.textwidget( + edit=self.prize_range_3_text, + text=pr3, + position=( + self.button_x + 365 + x_offs, + self.button_y + self.button_scale_y - 93 - 90 + prize_y_offs, + ), + ) + ba.textwidget( + edit=self.prize_value_3_text, + text=pv3, + scale=prize_value_scale_large + if ticket_char not in pv3 + else prize_value_scale_small, + position=( + self.button_x + 380 + x_offs, + self.button_y + self.button_scale_y - 93 - 90 + prize_y_offs, + ), + ) leader_name = '-' leader_score: str | ba.Lstr = '-' if entry['scores']: score = self.leader = copy.deepcopy(entry['scores'][0]) leader_name = score[1] - leader_score = (ba.timestring( - score[0] * 10, - centi=True, - timeformat=ba.TimeFormat.MILLISECONDS, - suppress_format_warning=True) - if entry['scoreType'] == 'time' else str(score[0])) + leader_score = ( + ba.timestring( + score[0] * 10, + centi=True, + timeformat=ba.TimeFormat.MILLISECONDS, + suppress_format_warning=True, + ) + if entry['scoreType'] == 'time' + else str(score[0]) + ) else: self.leader = None - ba.textwidget(edit=self.current_leader_name_text, - text=ba.Lstr(value=leader_name)) + ba.textwidget( + edit=self.current_leader_name_text, text=ba.Lstr(value=leader_name) + ) ba.textwidget(edit=self.current_leader_score_text, text=leader_score) - ba.buttonwidget(edit=self.more_scores_button, - label=ba.Lstr(resource=self._r + '.seeMoreText')) + ba.buttonwidget( + edit=self.more_scores_button, + label=ba.Lstr(resource=self._r + '.seeMoreText'), + ) out_of_time_text: str | ba.Lstr = ( - '-' if 'totalTime' not in entry else ba.Lstr( + '-' + if 'totalTime' not in entry + else ba.Lstr( resource=self._r + '.ofTotalTimeText', - subs=[('${TOTAL}', - ba.timestring(entry['totalTime'], - centi=False, - suppress_format_warning=True))])) - ba.textwidget(edit=self.time_remaining_out_of_text, - text=out_of_time_text) + subs=[ + ( + '${TOTAL}', + ba.timestring( + entry['totalTime'], + centi=False, + suppress_format_warning=True, + ), + ) + ], + ) + ) + ba.textwidget( + edit=self.time_remaining_out_of_text, text=out_of_time_text + ) self.time_remaining = entry['timeRemaining'] self.has_time_remaining = entry is not None self.tournament_id = entry['tournamentID'] - self.required_league = (None if 'requiredLeague' not in entry else - entry['requiredLeague']) + self.required_league = ( + None if 'requiredLeague' not in entry else entry['requiredLeague'] + ) game = ba.app.accounts_v1.tournament_info[self.tournament_id]['game'] if game is None: ba.textwidget(edit=self.button_text, text='-') - ba.imagewidget(edit=self.image, - texture=ba.gettexture('black'), - opacity=0.2) + ba.imagewidget( + edit=self.image, texture=ba.gettexture('black'), opacity=0.2 + ) else: campaignname, levelname = game.split(':') campaign = getcampaign(campaignname) max_players = ba.app.accounts_v1.tournament_info[ - self.tournament_id]['maxPlayers'] - txt = ba.Lstr(value='${A} ${B}', - subs=[('${A}', - campaign.getlevel(levelname).displayname), - ('${B}', - ba.Lstr(resource='playerCountAbbreviatedText', - subs=[('${COUNT}', str(max_players)) - ]))]) + self.tournament_id + ]['maxPlayers'] + txt = ba.Lstr( + value='${A} ${B}', + subs=[ + ('${A}', campaign.getlevel(levelname).displayname), + ( + '${B}', + ba.Lstr( + resource='playerCountAbbreviatedText', + subs=[('${COUNT}', str(max_players))], + ), + ), + ], + ) ba.textwidget(edit=self.button_text, text=txt) ba.imagewidget( edit=self.image, texture=campaign.getlevel(levelname).get_preview_texture(), - opacity=1.0 if enabled else 0.5) + opacity=1.0 if enabled else 0.5, + ) fee = entry['fee'] @@ -498,9 +605,11 @@ class TournamentButton: self.allow_ads = allow_ads = entry['allowAds'] - final_fee: int | None = (None if fee_var is None else - ba.internal.get_v1_account_misc_read_val( - fee_var, '?')) + final_fee: int | None = ( + None + if fee_var is None + else ba.internal.get_v1_account_misc_read_val(fee_var, '?') + ) final_fee_str: str | ba.Lstr if fee_var is None: @@ -509,53 +618,77 @@ class TournamentButton: if final_fee == 0: final_fee_str = ba.Lstr(resource='getTicketsWindow.freeText') else: - final_fee_str = (ba.charstr(ba.SpecialChar.TICKET_BACKING) + - str(final_fee)) + final_fee_str = ba.charstr(ba.SpecialChar.TICKET_BACKING) + str( + final_fee + ) ad_tries_remaining = ba.app.accounts_v1.tournament_info[ - self.tournament_id]['adTriesRemaining'] + self.tournament_id + ]['adTriesRemaining'] free_tries_remaining = ba.app.accounts_v1.tournament_info[ - self.tournament_id]['freeTriesRemaining'] + self.tournament_id + ]['freeTriesRemaining'] # Now, if this fee allows ads and we support video ads, show # the 'or ad' version. if allow_ads and ba.internal.has_video_ads(): ads_enabled = ba.internal.have_incentivized_ad() - ba.imagewidget(edit=self.entry_fee_ad_image, - opacity=1.0 if ads_enabled else 0.25) - or_text = ba.Lstr(resource='orText', - subs=[('${A}', ''), - ('${B}', '')]).evaluate().strip() + ba.imagewidget( + edit=self.entry_fee_ad_image, + opacity=1.0 if ads_enabled else 0.25, + ) + or_text = ( + ba.Lstr(resource='orText', subs=[('${A}', ''), ('${B}', '')]) + .evaluate() + .strip() + ) ba.textwidget(edit=self.entry_fee_text_or, text=or_text) - ba.textwidget(edit=self.entry_fee_text_top, - position=(self.button_x + 360, - self.button_y + self.button_scale_y - 60), - scale=1.3, - text=final_fee_str) + ba.textwidget( + edit=self.entry_fee_text_top, + position=( + self.button_x + 360, + self.button_y + self.button_scale_y - 60, + ), + scale=1.3, + text=final_fee_str, + ) # Possibly show number of ad-plays remaining. - ba.textwidget(edit=self.entry_fee_text_remaining, - position=(self.button_x + 360, - self.button_y + self.button_scale_y - 146), - text='' if ad_tries_remaining in [None, 0] else - ('' + str(ad_tries_remaining)), - color=(0.6, 0.6, 0.6, 1 if ads_enabled else 0.2)) + ba.textwidget( + edit=self.entry_fee_text_remaining, + position=( + self.button_x + 360, + self.button_y + self.button_scale_y - 146, + ), + text='' + if ad_tries_remaining in [None, 0] + else ('' + str(ad_tries_remaining)), + color=(0.6, 0.6, 0.6, 1 if ads_enabled else 0.2), + ) else: ba.imagewidget(edit=self.entry_fee_ad_image, opacity=0.0) ba.textwidget(edit=self.entry_fee_text_or, text='') - ba.textwidget(edit=self.entry_fee_text_top, - position=(self.button_x + 360, - self.button_y + self.button_scale_y - 80), - scale=1.3, - text=final_fee_str) + ba.textwidget( + edit=self.entry_fee_text_top, + position=( + self.button_x + 360, + self.button_y + self.button_scale_y - 80, + ), + scale=1.3, + text=final_fee_str, + ) # Possibly show number of free-plays remaining. ba.textwidget( edit=self.entry_fee_text_remaining, - position=(self.button_x + 360, - self.button_y + self.button_scale_y - 100), - text=('' if (free_tries_remaining in [None, 0] - or final_fee != 0) else - ('' + str(free_tries_remaining))), + position=( + self.button_x + 360, + self.button_y + self.button_scale_y - 100, + ), + text=( + '' + if (free_tries_remaining in [None, 0] or final_fee != 0) + else ('' + str(free_tries_remaining)) + ), color=(0.6, 0.6, 0.6, 1), ) diff --git a/dist/ba_data/python/bastd/ui/creditslist.py b/dist/ba_data/python/bastd/ui/creditslist.py index 7400005..6a79582 100644 --- a/dist/ba_data/python/bastd/ui/creditslist.py +++ b/dist/ba_data/python/bastd/ui/creditslist.py @@ -20,6 +20,7 @@ class CreditsListWindow(ba.Window): # pylint: disable=too-many-locals # pylint: disable=too-many-statements import json + ba.set_analytics_screen('Credits Window') # if they provided an origin-widget, scale up from that @@ -39,71 +40,93 @@ class CreditsListWindow(ba.Window): height = 398 if uiscale is ba.UIScale.SMALL else 500 self._r = 'creditsWindow' - super().__init__(root_widget=ba.containerwidget( - size=(width, height), - transition=transition, - toolbar_visibility='menu_minimal', - scale_origin_stack_offset=scale_origin, - scale=(2.0 if uiscale is ba.UIScale.SMALL else - 1.3 if uiscale is ba.UIScale.MEDIUM else 1.0), - stack_offset=(0, -8) if uiscale is ba.UIScale.SMALL else (0, 0))) + super().__init__( + root_widget=ba.containerwidget( + size=(width, height), + transition=transition, + toolbar_visibility='menu_minimal', + scale_origin_stack_offset=scale_origin, + scale=( + 2.0 + if uiscale is ba.UIScale.SMALL + else 1.3 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + stack_offset=(0, -8) if uiscale is ba.UIScale.SMALL else (0, 0), + ) + ) if ba.app.ui.use_toolbars and uiscale is ba.UIScale.SMALL: - ba.containerwidget(edit=self._root_widget, - on_cancel_call=self._back) + ba.containerwidget( + edit=self._root_widget, on_cancel_call=self._back + ) else: btn = ba.buttonwidget( parent=self._root_widget, - position=(40 + x_inset, height - - (68 if uiscale is ba.UIScale.SMALL else 62)), + position=( + 40 + x_inset, + height - (68 if uiscale is ba.UIScale.SMALL else 62), + ), size=(140, 60), scale=0.8, label=ba.Lstr(resource='backText'), button_type='back', on_activate_call=self._back, - autoselect=True) + autoselect=True, + ) ba.containerwidget(edit=self._root_widget, cancel_button=btn) ba.buttonwidget( edit=btn, button_type='backSmall', - position=(40 + x_inset, height - - (68 if uiscale is ba.UIScale.SMALL else 62) + 5), + position=( + 40 + x_inset, + height - (68 if uiscale is ba.UIScale.SMALL else 62) + 5, + ), size=(60, 48), - label=ba.charstr(ba.SpecialChar.BACK)) + label=ba.charstr(ba.SpecialChar.BACK), + ) - ba.textwidget(parent=self._root_widget, - position=(0, height - - (59 if uiscale is ba.UIScale.SMALL else 54)), - size=(width, 30), - text=ba.Lstr(resource=self._r + '.titleText', - subs=[('${APP_NAME}', - ba.Lstr(resource='titleText'))]), - h_align='center', - color=ba.app.ui.title_color, - maxwidth=330, - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(0, height - (59 if uiscale is ba.UIScale.SMALL else 54)), + size=(width, 30), + text=ba.Lstr( + resource=self._r + '.titleText', + subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))], + ), + h_align='center', + color=ba.app.ui.title_color, + maxwidth=330, + v_align='center', + ) - scroll = ba.scrollwidget(parent=self._root_widget, - position=(40 + x_inset, 35), - size=(width - (80 + 2 * x_inset), - height - 100), - capture_arrows=True) + scroll = ba.scrollwidget( + parent=self._root_widget, + position=(40 + x_inset, 35), + size=(width - (80 + 2 * x_inset), height - 100), + capture_arrows=True, + ) if ba.app.ui.use_toolbars: ba.widget( edit=scroll, - right_widget=ba.internal.get_special_widget('party_button')) + right_widget=ba.internal.get_special_widget('party_button'), + ) if uiscale is ba.UIScale.SMALL: ba.widget( edit=scroll, - left_widget=ba.internal.get_special_widget('back_button')) + left_widget=ba.internal.get_special_widget('back_button'), + ) def _format_names(names2: Sequence[str], inset: float) -> str: sval = '' # measure a series since there's overlaps and stuff.. - space_width = ba.internal.get_string_width( - ' ' * 10, suppress_warning=True) / 10.0 + space_width = ( + ba.internal.get_string_width(' ' * 10, suppress_warning=True) + / 10.0 + ) spacing = 330.0 col1 = inset col2 = col1 + spacing @@ -127,42 +150,69 @@ class CreditsListWindow(ba.Window): nline += spacingstr nline += name line_width = ba.internal.get_string_width( - nline, suppress_warning=True) + nline, suppress_warning=True + ) if nline != '': sval += nline + '\n' return sval - sound_and_music = ba.Lstr(resource=self._r + - '.songCreditText').evaluate() + sound_and_music = ba.Lstr( + resource=self._r + '.songCreditText' + ).evaluate() sound_and_music = sound_and_music.replace( - '${TITLE}', "'William Tell (Trumpet Entry)'") + '${TITLE}', "'William Tell (Trumpet Entry)'" + ) sound_and_music = sound_and_music.replace( - '${PERFORMER}', 'The Apollo Symphony Orchestra') + '${PERFORMER}', 'The Apollo Symphony Orchestra' + ) sound_and_music = sound_and_music.replace( - '${PERFORMER}', 'The Apollo Symphony Orchestra') - sound_and_music = sound_and_music.replace('${COMPOSER}', - 'Gioacchino Rossini') + '${PERFORMER}', 'The Apollo Symphony Orchestra' + ) + sound_and_music = sound_and_music.replace( + '${COMPOSER}', 'Gioacchino Rossini' + ) sound_and_music = sound_and_music.replace('${ARRANGER}', 'Chris Worth') sound_and_music = sound_and_music.replace('${PUBLISHER}', 'BMI') - sound_and_music = sound_and_music.replace('${SOURCE}', - 'www.AudioSparx.com') + sound_and_music = sound_and_music.replace( + '${SOURCE}', 'www.AudioSparx.com' + ) spc = ' ' sound_and_music = spc + sound_and_music.replace('\n', '\n' + spc) names = [ - 'HubOfTheUniverseProd', 'Jovica', 'LG', 'Leady', 'Percy Duke', - 'PhreaKsAccount', 'Pogotron', 'Rock Savage', 'anamorphosis', - 'benboncan', 'cdrk', 'chipfork', 'guitarguy1985', 'jascha', - 'joedeshon', 'loofa', 'm_O_m', 'mich3d', 'sandyrb', 'shakaharu', - 'sirplus', 'stickman', 'thanvannispen', 'virotic', 'zimbot' + 'HubOfTheUniverseProd', + 'Jovica', + 'LG', + 'Leady', + 'Percy Duke', + 'PhreaKsAccount', + 'Pogotron', + 'Rock Savage', + 'anamorphosis', + 'benboncan', + 'cdrk', + 'chipfork', + 'guitarguy1985', + 'jascha', + 'joedeshon', + 'loofa', + 'm_O_m', + 'mich3d', + 'sandyrb', + 'shakaharu', + 'sirplus', + 'stickman', + 'thanvannispen', + 'virotic', + 'zimbot', ] names.sort(key=lambda x: x.lower()) freesound_names = _format_names(names, 90) try: - with open('ba_data/data/langdata.json', - encoding='utf-8') as infile: - translation_contributors = (json.loads( - infile.read())['translation_contributors']) + with open('ba_data/data/langdata.json', encoding='utf-8') as infile: + translation_contributors = json.loads(infile.read())[ + 'translation_contributors' + ] except Exception: ba.print_exception('Error reading translation contributors.') translation_contributors = [] @@ -174,41 +224,54 @@ class CreditsListWindow(ba.Window): # We can remove that limit once we drop support for GL ES2.. :-/ # (or add mesh splitting under the hood) credits_text = ( - ' ' + ba.Lstr(resource=self._r + - '.codingGraphicsAudioText').evaluate().replace( - '${NAME}', 'Eric Froemling') + '\n' + ' ' + + ba.Lstr(resource=self._r + '.codingGraphicsAudioText') + .evaluate() + .replace('${NAME}', 'Eric Froemling') + + '\n' '\n' - ' ' + ba.Lstr(resource=self._r + - '.additionalAudioArtIdeasText').evaluate().replace( - '${NAME}', 'Raphael Suter') + '\n' + ' ' + + ba.Lstr(resource=self._r + '.additionalAudioArtIdeasText') + .evaluate() + .replace('${NAME}', 'Raphael Suter') + + '\n' '\n' - ' ' + - ba.Lstr(resource=self._r + '.soundAndMusicText').evaluate() + '\n' + ' ' + + ba.Lstr(resource=self._r + '.soundAndMusicText').evaluate() + + '\n' '\n' + sound_and_music + '\n' '\n' - ' ' + ba.Lstr(resource=self._r + - '.publicDomainMusicViaText').evaluate().replace( - '${NAME}', 'Musopen.com') + '\n' - ' ' + - ba.Lstr(resource=self._r + - '.thanksEspeciallyToText').evaluate().replace( - '${NAME}', 'the US Army, Navy, and Marine Bands') + + ' ' + + ba.Lstr(resource=self._r + '.publicDomainMusicViaText') + .evaluate() + .replace('${NAME}', 'Musopen.com') + + '\n' + ' ' + + ba.Lstr(resource=self._r + '.thanksEspeciallyToText') + .evaluate() + .replace('${NAME}', 'the US Army, Navy, and Marine Bands') + + '\n' '\n' + ' ' + + ba.Lstr(resource=self._r + '.additionalMusicFromText') + .evaluate() + .replace('${NAME}', 'The YouTube Audio Library') + + '\n' '\n' - ' ' + ba.Lstr(resource=self._r + - '.additionalMusicFromText').evaluate().replace( - '${NAME}', 'The YouTube Audio Library') + - '\n' - '\n' - ' ' + - ba.Lstr(resource=self._r + '.soundsText').evaluate().replace( - '${SOURCE}', 'Freesound.org') + '\n' + ' ' + + ba.Lstr(resource=self._r + '.soundsText') + .evaluate() + .replace('${SOURCE}', 'Freesound.org') + + '\n' '\n' + freesound_names + '\n' '\n' - ' ' + ba.Lstr(resource=self._r + - '.languageTranslationsText').evaluate() + '\n' - '\n' + '\n'.join(translation_names.splitlines()[:146]) + - '\n'.join(translation_names.splitlines()[146:]) + '\n' + ' ' + + ba.Lstr(resource=self._r + '.languageTranslationsText').evaluate() + + '\n' + '\n' + + '\n'.join(translation_names.splitlines()[:146]) + + '\n'.join(translation_names.splitlines()[146:]) + + '\n' '\n' ' Shout Out to Awesome Mods / Modders / Contributors:\n\n' ' BombDash ModPack\n' @@ -222,24 +285,33 @@ class CreditsListWindow(ba.Window): '\n' ' Holiday theme vector art designed by Freepik\n' '\n' - ' ' + - ba.Lstr(resource=self._r + '.specialThanksText').evaluate() + '\n' + ' ' + + ba.Lstr(resource=self._r + '.specialThanksText').evaluate() + + '\n' '\n' ' Todd, Laura, and Robert Froemling\n' - ' ' + - ba.Lstr(resource=self._r + '.allMyFamilyText').evaluate().replace( - '\n', '\n ') + '\n' - ' ' + ba.Lstr(resource=self._r + - '.whoeverInventedCoffeeText').evaluate() + '\n' + ' ' + + ba.Lstr(resource=self._r + '.allMyFamilyText') + .evaluate() + .replace('\n', '\n ') + + '\n' + ' ' + + ba.Lstr( + resource=self._r + '.whoeverInventedCoffeeText' + ).evaluate() + + '\n' '\n' ' ' + ba.Lstr(resource=self._r + '.legalText').evaluate() + '\n' '\n' - ' ' + ba.Lstr(resource=self._r + - '.softwareBasedOnText').evaluate().replace( - '${NAME}', 'the Khronos Group') + '\n' + ' ' + + ba.Lstr(resource=self._r + '.softwareBasedOnText') + .evaluate() + .replace('${NAME}', 'the Khronos Group') + + '\n' '\n' ' ' - ' www.ballistica.net\n') + ' www.ballistica.net\n' + ) txt = credits_text lines = txt.splitlines() @@ -254,25 +326,31 @@ class CreditsListWindow(ba.Window): size=(self._sub_width, self._sub_height), background=False, claims_left_right=False, - claims_tab=False) + claims_tab=False, + ) voffs = 0 for line in lines: - ba.textwidget(parent=container, - padding=4, - color=(0.7, 0.9, 0.7, 1.0), - scale=scale, - flatness=1.0, - size=(0, 0), - position=(0, self._sub_height - 20 + voffs), - h_align='left', - v_align='top', - text=ba.Lstr(value=line)) + ba.textwidget( + parent=container, + padding=4, + color=(0.7, 0.9, 0.7, 1.0), + scale=scale, + flatness=1.0, + size=(0, 0), + position=(0, self._sub_height - 20 + voffs), + h_align='left', + v_align='top', + text=ba.Lstr(value=line), + ) voffs -= line_height def _back(self) -> None: from bastd.ui.mainmenu import MainMenuWindow - ba.containerwidget(edit=self._root_widget, - transition=self._transition_out) + + ba.containerwidget( + edit=self._root_widget, transition=self._transition_out + ) ba.app.ui.set_main_menu_window( - MainMenuWindow(transition='in_left').get_root_widget()) + MainMenuWindow(transition='in_left').get_root_widget() + ) diff --git a/dist/ba_data/python/bastd/ui/debug.py b/dist/ba_data/python/bastd/ui/debug.py index e896454..888fd15 100644 --- a/dist/ba_data/python/bastd/ui/debug.py +++ b/dist/ba_data/python/bastd/ui/debug.py @@ -23,8 +23,13 @@ class DebugWindow(ba.Window): ba.app.ui.set_main_menu_location('Benchmarks & Stress Tests') uiscale = ba.app.ui.uiscale self._width = width = 580 - self._height = height = (350 if uiscale is ba.UIScale.SMALL else - 420 if uiscale is ba.UIScale.MEDIUM else 520) + self._height = height = ( + 350 + if uiscale is ba.UIScale.SMALL + else 420 + if uiscale is ba.UIScale.MEDIUM + else 520 + ) self._scroll_width = self._width - 100 self._scroll_height = self._height - 120 @@ -39,12 +44,22 @@ class DebugWindow(ba.Window): self._r = 'debugWindow' uiscale = ba.app.ui.uiscale - super().__init__(root_widget=ba.containerwidget( - size=(width, height), - transition=transition, - scale=(2.35 if uiscale is ba.UIScale.SMALL else - 1.55 if uiscale is ba.UIScale.MEDIUM else 1.0), - stack_offset=(0, -30) if uiscale is ba.UIScale.SMALL else (0, 0))) + super().__init__( + root_widget=ba.containerwidget( + size=(width, height), + transition=transition, + scale=( + 2.35 + if uiscale is ba.UIScale.SMALL + else 1.55 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + stack_offset=(0, -30) + if uiscale is ba.UIScale.SMALL + else (0, 0), + ) + ) self._done_button = btn = ba.buttonwidget( parent=self._root_widget, @@ -53,28 +68,33 @@ class DebugWindow(ba.Window): scale=0.8, autoselect=True, label=ba.Lstr(resource='doneText'), - on_activate_call=self._done) + on_activate_call=self._done, + ) ba.containerwidget(edit=self._root_widget, cancel_button=btn) - ba.textwidget(parent=self._root_widget, - position=(0, height - 60), - size=(width, 30), - text=ba.Lstr(resource=self._r + '.titleText'), - h_align='center', - color=ba.app.ui.title_color, - v_align='center', - maxwidth=260) + ba.textwidget( + parent=self._root_widget, + position=(0, height - 60), + size=(width, 30), + text=ba.Lstr(resource=self._r + '.titleText'), + h_align='center', + color=ba.app.ui.title_color, + v_align='center', + maxwidth=260, + ) self._scrollwidget = ba.scrollwidget( parent=self._root_widget, highlight=False, size=(self._scroll_width, self._scroll_height), - position=((self._width - self._scroll_width) * 0.5, 50)) + position=((self._width - self._scroll_width) * 0.5, 50), + ) ba.containerwidget(edit=self._scrollwidget, claims_left_right=True) - self._subcontainer = ba.containerwidget(parent=self._scrollwidget, - size=(self._sub_width, - self._sub_height), - background=False) + self._subcontainer = ba.containerwidget( + parent=self._scrollwidget, + size=(self._sub_width, self._sub_height), + background=False, + ) v = self._sub_height - 70 button_width = 300 @@ -84,19 +104,21 @@ class DebugWindow(ba.Window): size=(button_width, 60), autoselect=True, label=ba.Lstr(resource=self._r + '.runCPUBenchmarkText'), - on_activate_call=self._run_cpu_benchmark_pressed) - ba.widget(edit=btn, - up_widget=self._done_button, - left_widget=self._done_button) + on_activate_call=self._run_cpu_benchmark_pressed, + ) + ba.widget( + edit=btn, up_widget=self._done_button, left_widget=self._done_button + ) v -= 60 - ba.buttonwidget(parent=self._subcontainer, - position=((self._sub_width - button_width) * 0.5, v), - size=(button_width, 60), - autoselect=True, - label=ba.Lstr(resource=self._r + - '.runGPUBenchmarkText'), - on_activate_call=self._run_gpu_benchmark_pressed) + ba.buttonwidget( + parent=self._subcontainer, + position=((self._sub_width - button_width) * 0.5, v), + size=(button_width, 60), + autoselect=True, + label=ba.Lstr(resource=self._r + '.runGPUBenchmarkText'), + on_activate_call=self._run_gpu_benchmark_pressed, + ) v -= 60 ba.buttonwidget( @@ -105,31 +127,35 @@ class DebugWindow(ba.Window): size=(button_width, 60), autoselect=True, label=ba.Lstr(resource=self._r + '.runMediaReloadBenchmarkText'), - on_activate_call=self._run_media_reload_benchmark_pressed) + on_activate_call=self._run_media_reload_benchmark_pressed, + ) v -= 60 - ba.textwidget(parent=self._subcontainer, - position=(self._sub_width * 0.5, v + 22), - size=(0, 0), - text=ba.Lstr(resource=self._r + '.stressTestTitleText'), - maxwidth=200, - color=ba.app.ui.heading_color, - scale=0.85, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._subcontainer, + position=(self._sub_width * 0.5, v + 22), + size=(0, 0), + text=ba.Lstr(resource=self._r + '.stressTestTitleText'), + maxwidth=200, + color=ba.app.ui.heading_color, + scale=0.85, + h_align='center', + v_align='center', + ) v -= 45 x_offs = 165 - ba.textwidget(parent=self._subcontainer, - position=(x_offs - 10, v + 22), - size=(0, 0), - text=ba.Lstr(resource=self._r + - '.stressTestPlaylistTypeText'), - maxwidth=130, - color=ba.app.ui.heading_color, - scale=0.65, - h_align='right', - v_align='center') + ba.textwidget( + parent=self._subcontainer, + position=(x_offs - 10, v + 22), + size=(0, 0), + text=ba.Lstr(resource=self._r + '.stressTestPlaylistTypeText'), + maxwidth=130, + color=ba.app.ui.heading_color, + scale=0.65, + h_align='right', + v_align='center', + ) popup.PopupMenu( parent=self._subcontainer, @@ -137,25 +163,29 @@ class DebugWindow(ba.Window): width=150, choices=['Random', 'Teams', 'Free-For-All'], choices_display=[ - ba.Lstr(resource=a) for a in [ - 'randomText', 'playModes.teamsText', - 'playModes.freeForAllText' + ba.Lstr(resource=a) + for a in [ + 'randomText', + 'playModes.teamsText', + 'playModes.freeForAllText', ] ], current_choice='Auto', - on_value_change_call=self._stress_test_game_type_selected) + on_value_change_call=self._stress_test_game_type_selected, + ) v -= 46 - ba.textwidget(parent=self._subcontainer, - position=(x_offs - 10, v + 22), - size=(0, 0), - text=ba.Lstr(resource=self._r + - '.stressTestPlaylistNameText'), - maxwidth=130, - color=ba.app.ui.heading_color, - scale=0.65, - h_align='right', - v_align='center') + ba.textwidget( + parent=self._subcontainer, + position=(x_offs - 10, v + 22), + size=(0, 0), + text=ba.Lstr(resource=self._r + '.stressTestPlaylistNameText'), + maxwidth=130, + color=ba.app.ui.heading_color, + scale=0.65, + h_align='right', + v_align='center', + ) self._stress_test_playlist_name_field = ba.textwidget( parent=self._subcontainer, @@ -166,24 +196,27 @@ class DebugWindow(ba.Window): v_align='center', autoselect=True, color=(0.9, 0.9, 0.9, 1.0), - description=ba.Lstr(resource=self._r + - '.stressTestPlaylistDescriptionText'), + description=ba.Lstr( + resource=self._r + '.stressTestPlaylistDescriptionText' + ), editable=True, - padding=4) + padding=4, + ) v -= 29 x_sub = 60 # Player count. - ba.textwidget(parent=self._subcontainer, - position=(x_offs - 10, v), - size=(0, 0), - text=ba.Lstr(resource=self._r + - '.stressTestPlayerCountText'), - color=(0.8, 0.8, 0.8, 1.0), - h_align='right', - v_align='center', - scale=0.65, - maxwidth=130) + ba.textwidget( + parent=self._subcontainer, + position=(x_offs - 10, v), + size=(0, 0), + text=ba.Lstr(resource=self._r + '.stressTestPlayerCountText'), + color=(0.8, 0.8, 0.8, 1.0), + h_align='right', + v_align='center', + scale=0.65, + maxwidth=130, + ) self._stress_test_player_count_text = ba.textwidget( parent=self._subcontainer, position=(246 - x_sub, v - 14), @@ -193,38 +226,42 @@ class DebugWindow(ba.Window): h_align='right', v_align='center', text=str(self._stress_test_player_count), - padding=2) - ba.buttonwidget(parent=self._subcontainer, - position=(330 - x_sub, v - 11), - size=(28, 28), - label='-', - autoselect=True, - on_activate_call=ba.Call( - self._stress_test_player_count_decrement), - repeat=True, - enable_sound=True) - ba.buttonwidget(parent=self._subcontainer, - position=(380 - x_sub, v - 11), - size=(28, 28), - label='+', - autoselect=True, - on_activate_call=ba.Call( - self._stress_test_player_count_increment), - repeat=True, - enable_sound=True) + padding=2, + ) + ba.buttonwidget( + parent=self._subcontainer, + position=(330 - x_sub, v - 11), + size=(28, 28), + label='-', + autoselect=True, + on_activate_call=ba.Call(self._stress_test_player_count_decrement), + repeat=True, + enable_sound=True, + ) + ba.buttonwidget( + parent=self._subcontainer, + position=(380 - x_sub, v - 11), + size=(28, 28), + label='+', + autoselect=True, + on_activate_call=ba.Call(self._stress_test_player_count_increment), + repeat=True, + enable_sound=True, + ) v -= 42 # Round duration. - ba.textwidget(parent=self._subcontainer, - position=(x_offs - 10, v), - size=(0, 0), - text=ba.Lstr(resource=self._r + - '.stressTestRoundDurationText'), - color=(0.8, 0.8, 0.8, 1.0), - h_align='right', - v_align='center', - scale=0.65, - maxwidth=130) + ba.textwidget( + parent=self._subcontainer, + position=(x_offs - 10, v), + size=(0, 0), + text=ba.Lstr(resource=self._r + '.stressTestRoundDurationText'), + color=(0.8, 0.8, 0.8, 1.0), + h_align='right', + v_align='center', + scale=0.65, + maxwidth=130, + ) self._stress_test_round_duration_text = ba.textwidget( parent=self._subcontainer, position=(246 - x_sub, v - 14), @@ -234,25 +271,32 @@ class DebugWindow(ba.Window): h_align='right', v_align='center', text=str(self._stress_test_round_duration), - padding=2) - ba.buttonwidget(parent=self._subcontainer, - position=(330 - x_sub, v - 11), - size=(28, 28), - label='-', - autoselect=True, - on_activate_call=ba.Call( - self._stress_test_round_duration_decrement), - repeat=True, - enable_sound=True) - ba.buttonwidget(parent=self._subcontainer, - position=(380 - x_sub, v - 11), - size=(28, 28), - label='+', - autoselect=True, - on_activate_call=ba.Call( - self._stress_test_round_duration_increment), - repeat=True, - enable_sound=True) + padding=2, + ) + ba.buttonwidget( + parent=self._subcontainer, + position=(330 - x_sub, v - 11), + size=(28, 28), + label='-', + autoselect=True, + on_activate_call=ba.Call( + self._stress_test_round_duration_decrement + ), + repeat=True, + enable_sound=True, + ) + ba.buttonwidget( + parent=self._subcontainer, + position=(380 - x_sub, v - 11), + size=(28, 28), + label='+', + autoselect=True, + on_activate_call=ba.Call( + self._stress_test_round_duration_increment + ), + repeat=True, + enable_sound=True, + ) v -= 82 btn = ba.buttonwidget( parent=self._subcontainer, @@ -260,61 +304,78 @@ class DebugWindow(ba.Window): size=(button_width, 60), autoselect=True, label=ba.Lstr(resource=self._r + '.runStressTestText'), - on_activate_call=self._stress_test_pressed) + on_activate_call=self._stress_test_pressed, + ) ba.widget(btn, show_buffer_bottom=50) def _stress_test_player_count_decrement(self) -> None: self._stress_test_player_count = max( - 1, self._stress_test_player_count - 1) - ba.textwidget(edit=self._stress_test_player_count_text, - text=str(self._stress_test_player_count)) + 1, self._stress_test_player_count - 1 + ) + ba.textwidget( + edit=self._stress_test_player_count_text, + text=str(self._stress_test_player_count), + ) def _stress_test_player_count_increment(self) -> None: self._stress_test_player_count = self._stress_test_player_count + 1 - ba.textwidget(edit=self._stress_test_player_count_text, - text=str(self._stress_test_player_count)) + ba.textwidget( + edit=self._stress_test_player_count_text, + text=str(self._stress_test_player_count), + ) def _stress_test_round_duration_decrement(self) -> None: self._stress_test_round_duration = max( - 10, self._stress_test_round_duration - 10) - ba.textwidget(edit=self._stress_test_round_duration_text, - text=str(self._stress_test_round_duration)) + 10, self._stress_test_round_duration - 10 + ) + ba.textwidget( + edit=self._stress_test_round_duration_text, + text=str(self._stress_test_round_duration), + ) def _stress_test_round_duration_increment(self) -> None: - self._stress_test_round_duration = (self._stress_test_round_duration + - 10) - ba.textwidget(edit=self._stress_test_round_duration_text, - text=str(self._stress_test_round_duration)) + self._stress_test_round_duration = self._stress_test_round_duration + 10 + ba.textwidget( + edit=self._stress_test_round_duration_text, + text=str(self._stress_test_round_duration), + ) def _stress_test_game_type_selected(self, game_type: str) -> None: self._stress_test_game_type = game_type def _run_cpu_benchmark_pressed(self) -> None: from ba.internal import run_cpu_benchmark + run_cpu_benchmark() def _run_gpu_benchmark_pressed(self) -> None: from ba.internal import run_gpu_benchmark + run_gpu_benchmark() def _run_media_reload_benchmark_pressed(self) -> None: from ba.internal import run_media_reload_benchmark + run_media_reload_benchmark() def _stress_test_pressed(self) -> None: from ba.internal import run_stress_test + run_stress_test( playlist_type=self._stress_test_game_type, playlist_name=cast( - str, - ba.textwidget(query=self._stress_test_playlist_name_field)), + str, ba.textwidget(query=self._stress_test_playlist_name_field) + ), player_count=self._stress_test_player_count, - round_duration=self._stress_test_round_duration) + round_duration=self._stress_test_round_duration, + ) ba.containerwidget(edit=self._root_widget, transition='out_right') def _done(self) -> None: # pylint: disable=cyclic-import from bastd.ui.settings.advanced import AdvancedSettingsWindow + ba.containerwidget(edit=self._root_widget, transition='out_right') ba.app.ui.set_main_menu_window( - AdvancedSettingsWindow(transition='in_left').get_root_widget()) + AdvancedSettingsWindow(transition='in_left').get_root_widget() + ) diff --git a/dist/ba_data/python/bastd/ui/feedback.py b/dist/ba_data/python/bastd/ui/feedback.py index c4b4665..b640d97 100644 --- a/dist/ba_data/python/bastd/ui/feedback.py +++ b/dist/ba_data/python/bastd/ui/feedback.py @@ -22,8 +22,10 @@ def ask_for_rating() -> ba.Widget | None: # FIXME: should whitelist platforms we *do* want this for. if ba.app.test_build: return None - if not (platform == 'mac' or (platform == 'android' - and subplatform in ['google', 'cardboard'])): + if not ( + platform == 'mac' + or (platform == 'android' and subplatform in ['google', 'cardboard']) + ): return None width = 700 height = 400 @@ -32,27 +34,38 @@ def ask_for_rating() -> ba.Widget | None: dlg = ba.containerwidget( size=(width, height), transition='in_right', - scale=(1.6 if uiscale is ba.UIScale.SMALL else - 1.35 if uiscale is ba.UIScale.MEDIUM else 1.0)) + scale=( + 1.6 + if uiscale is ba.UIScale.SMALL + else 1.35 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + ) v = height - 50 v -= spacing v -= 140 - ba.imagewidget(parent=dlg, - position=(width / 2 - 100, v + 10), - size=(200, 200), - texture=ba.gettexture('cuteSpaz')) - ba.textwidget(parent=dlg, - position=(15, v - 55), - size=(width - 30, 30), - color=ba.app.ui.infotextcolor, - text=ba.Lstr(resource='pleaseRateText', - subs=[('${APP_NAME}', - ba.Lstr(resource='titleText'))]), - maxwidth=width * 0.95, - max_height=130, - scale=0.85, - h_align='center', - v_align='center') + ba.imagewidget( + parent=dlg, + position=(width / 2 - 100, v + 10), + size=(200, 200), + texture=ba.gettexture('cuteSpaz'), + ) + ba.textwidget( + parent=dlg, + position=(15, v - 55), + size=(width - 30, 30), + color=ba.app.ui.infotextcolor, + text=ba.Lstr( + resource='pleaseRateText', + subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))], + ), + maxwidth=width * 0.95, + max_height=130, + scale=0.85, + h_align='center', + v_align='center', + ) def do_rating() -> None: if platform == 'android': @@ -67,21 +80,25 @@ def ask_for_rating() -> ba.Widget | None: ba.open_url(url) ba.containerwidget(edit=dlg, transition='out_left') - ba.buttonwidget(parent=dlg, - position=(60, 20), - size=(200, 60), - label=ba.Lstr(resource='wellSureText'), - autoselect=True, - on_activate_call=do_rating) + ba.buttonwidget( + parent=dlg, + position=(60, 20), + size=(200, 60), + label=ba.Lstr(resource='wellSureText'), + autoselect=True, + on_activate_call=do_rating, + ) def close() -> None: ba.containerwidget(edit=dlg, transition='out_left') - btn = ba.buttonwidget(parent=dlg, - position=(width - 270, 20), - size=(200, 60), - label=ba.Lstr(resource='noThanksText'), - autoselect=True, - on_activate_call=close) + btn = ba.buttonwidget( + parent=dlg, + position=(width - 270, 20), + size=(200, 60), + label=ba.Lstr(resource='noThanksText'), + autoselect=True, + on_activate_call=close, + ) ba.containerwidget(edit=dlg, cancel_button=btn, selected_child=btn) return dlg diff --git a/dist/ba_data/python/bastd/ui/fileselector.py b/dist/ba_data/python/bastd/ui/fileselector.py index 1f0963e..a3876d5 100644 --- a/dist/ba_data/python/bastd/ui/fileselector.py +++ b/dist/ba_data/python/bastd/ui/fileselector.py @@ -19,12 +19,14 @@ if TYPE_CHECKING: class FileSelectorWindow(ba.Window): """Window for selecting files.""" - def __init__(self, - path: str, - callback: Callable[[str | None], Any] | None = None, - show_base_path: bool = True, - valid_file_extensions: Sequence[str] | None = None, - allow_folders: bool = False): + def __init__( + self, + path: str, + callback: Callable[[str | None], Any] | None = None, + show_base_path: bool = True, + valid_file_extensions: Sequence[str] | None = None, + allow_folders: bool = False, + ): if valid_file_extensions is None: valid_file_extensions = [] uiscale = ba.app.ui.uiscale @@ -45,12 +47,22 @@ class FileSelectorWindow(ba.Window): self._scroll_width = self._width - (80 + 2 * x_inset) self._scroll_height = self._height - 170 self._r = 'fileSelectorWindow' - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height), - transition='in_right', - scale=(2.23 if uiscale is ba.UIScale.SMALL else - 1.4 if uiscale is ba.UIScale.MEDIUM else 1.0), - stack_offset=(0, -35) if uiscale is ba.UIScale.SMALL else (0, 0))) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height), + transition='in_right', + scale=( + 2.23 + if uiscale is ba.UIScale.SMALL + else 1.4 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + stack_offset=(0, -35) + if uiscale is ba.UIScale.SMALL + else (0, 0), + ) + ) ba.textwidget( parent=self._root_widget, position=(self._width * 0.5, self._height - 42), @@ -58,12 +70,13 @@ class FileSelectorWindow(ba.Window): color=ba.app.ui.title_color, h_align='center', v_align='center', - text=ba.Lstr(resource=self._r + '.titleFolderText') if - (allow_folders and not valid_file_extensions) else ba.Lstr( - resource=self._r + - '.titleFileText') if not allow_folders else ba.Lstr( - resource=self._r + '.titleFileFolderText'), - maxwidth=210) + text=ba.Lstr(resource=self._r + '.titleFolderText') + if (allow_folders and not valid_file_extensions) + else ba.Lstr(resource=self._r + '.titleFileText') + if not allow_folders + else ba.Lstr(resource=self._r + '.titleFileFolderText'), + maxwidth=210, + ) self._button_width = 146 self._cancel_button = ba.buttonwidget( @@ -72,7 +85,8 @@ class FileSelectorWindow(ba.Window): autoselect=True, size=(self._button_width, 50), label=ba.Lstr(resource='cancelText'), - on_activate_call=self._cancel) + on_activate_call=self._cancel, + ) ba.widget(edit=self._cancel_button, left_widget=self._cancel_button) b_color = (0.6, 0.53, 0.63) @@ -86,7 +100,8 @@ class FileSelectorWindow(ba.Window): enable_sound=False, size=(55, 35), label=ba.charstr(ba.SpecialChar.LEFT_ARROW), - on_activate_call=self._on_back_press) + on_activate_call=self._on_back_press, + ) self._folder_tex = ba.gettexture('folder') self._folder_color = (1.1, 0.8, 0.2) @@ -94,23 +109,27 @@ class FileSelectorWindow(ba.Window): self._file_color = (1, 1, 1) self._use_folder_button: ba.Widget | None = None self._folder_center = self._width * 0.5 + 15 - self._folder_icon = ba.imagewidget(parent=self._root_widget, - size=(40, 40), - position=(40, self._height - 117), - texture=self._folder_tex, - color=self._folder_color) - self._path_text = ba.textwidget(parent=self._root_widget, - position=(self._folder_center, - self._height - 98), - size=(0, 0), - color=ba.app.ui.title_color, - h_align='center', - v_align='center', - text=self._path, - maxwidth=self._width * 0.9) + self._folder_icon = ba.imagewidget( + parent=self._root_widget, + size=(40, 40), + position=(40, self._height - 117), + texture=self._folder_tex, + color=self._folder_color, + ) + self._path_text = ba.textwidget( + parent=self._root_widget, + position=(self._folder_center, self._height - 98), + size=(0, 0), + color=ba.app.ui.title_color, + h_align='center', + v_align='center', + text=self._path, + maxwidth=self._width * 0.9, + ) self._scrollwidget: ba.Widget | None = None - ba.containerwidget(edit=self._root_widget, - cancel_button=self._cancel_button) + ba.containerwidget( + edit=self._root_widget, cancel_button=self._cancel_button + ) self._set_path(path) def _on_up_press(self) -> None: @@ -154,26 +173,32 @@ class FileSelectorWindow(ba.Window): elif os.path.isfile(test_path): if self._is_valid_file_path(test_path): ba.playsound(ba.getsound('swish')) - ba.containerwidget(edit=self._root_widget, - transition='out_right') + ba.containerwidget( + edit=self._root_widget, transition='out_right' + ) if self._callback is not None: self._callback(test_path) else: ba.playsound(ba.getsound('error')) else: - print(('Error: FileSelectorWindow found non-file/dir:', - test_path)) + print( + ( + 'Error: FileSelectorWindow found non-file/dir:', + test_path, + ) + ) except Exception: ba.print_exception( - 'Error in FileSelectorWindow._on_entry_activated().') + 'Error in FileSelectorWindow._on_entry_activated().' + ) if new_path is not None: self._set_path(new_path) class _RefreshThread(threading.Thread): - - def __init__(self, path: str, - callback: Callable[[list[str], str | None], Any]): + def __init__( + self, path: str, callback: Callable[[list[str], str | None], Any] + ): super().__init__() self._callback = callback self._path = path @@ -189,15 +214,18 @@ class FileSelectorWindow(ba.Window): # has time to see the selection highlight. if duration < min_time: time.sleep(min_time - duration) - ba.pushcall(ba.Call(self._callback, files, None), - from_other_thread=True) + ba.pushcall( + ba.Call(self._callback, files, None), from_other_thread=True + ) except Exception as exc: # Ignore permission-denied. if 'Errno 13' not in str(exc): ba.print_exception() nofiles: list[str] = [] - ba.pushcall(ba.Call(self._callback, nofiles, str(exc)), - from_other_thread=True) + ba.pushcall( + ba.Call(self._callback, nofiles, str(exc)), + from_other_thread=True, + ) def _set_path(self, path: str, add_to_recent: bool = True) -> None: self._path = path @@ -212,11 +240,12 @@ class FileSelectorWindow(ba.Window): if not self._root_widget: return - scrollwidget_selected = (self._scrollwidget is None - or self._root_widget.get_selected_child() - == self._scrollwidget) + scrollwidget_selected = ( + self._scrollwidget is None + or self._root_widget.get_selected_child() == self._scrollwidget + ) - in_top_folder = (self._path == self._base_path) + in_top_folder = self._path == self._base_path hide_top_folder = in_top_folder and self._show_base_path is False if hide_top_folder: @@ -231,25 +260,34 @@ class FileSelectorWindow(ba.Window): b_color_disabled = (0.65, 0.65, 0.65) if len(self._recent_paths) < 2: - ba.buttonwidget(edit=self._back_button, - color=b_color_disabled, - textcolor=(0.5, 0.5, 0.5)) + ba.buttonwidget( + edit=self._back_button, + color=b_color_disabled, + textcolor=(0.5, 0.5, 0.5), + ) else: - ba.buttonwidget(edit=self._back_button, - color=b_color, - textcolor=(0.75, 0.7, 0.8)) + ba.buttonwidget( + edit=self._back_button, + color=b_color, + textcolor=(0.75, 0.7, 0.8), + ) max_str_width = 300.0 str_width = min( max_str_width, - ba.internal.get_string_width(folder_name, suppress_warning=True)) - ba.textwidget(edit=self._path_text, - text=folder_name, - maxwidth=max_str_width) - ba.imagewidget(edit=self._folder_icon, - position=(self._folder_center - str_width * 0.5 - 40, - self._height - 117), - opacity=0.0 if hide_top_folder else 1.0) + ba.internal.get_string_width(folder_name, suppress_warning=True), + ) + ba.textwidget( + edit=self._path_text, text=folder_name, maxwidth=max_str_width + ) + ba.imagewidget( + edit=self._folder_icon, + position=( + self._folder_center - str_width * 0.5 - 40, + self._height - 117, + ), + opacity=0.0 if hide_top_folder else 1.0, + ) if self._scrollwidget is not None: self._scrollwidget.delete() @@ -260,29 +298,38 @@ class FileSelectorWindow(ba.Window): self._scrollwidget = ba.scrollwidget( parent=self._root_widget, - position=((self._width - self._scroll_width) * 0.5, - self._height - self._scroll_height - 119), - size=(self._scroll_width, self._scroll_height)) + position=( + (self._width - self._scroll_width) * 0.5, + self._height - self._scroll_height - 119, + ), + size=(self._scroll_width, self._scroll_height), + ) if scrollwidget_selected: - ba.containerwidget(edit=self._root_widget, - selected_child=self._scrollwidget) + ba.containerwidget( + edit=self._root_widget, selected_child=self._scrollwidget + ) # show error case.. if error is not None: - self._subcontainer = ba.containerwidget(parent=self._scrollwidget, - size=(self._scroll_width, - self._scroll_height), - background=False) - ba.textwidget(parent=self._subcontainer, - color=(1, 1, 0, 1), - text=error, - maxwidth=self._scroll_width * 0.9, - position=(self._scroll_width * 0.48, - self._scroll_height * 0.57), - size=(0, 0), - h_align='center', - v_align='center') + self._subcontainer = ba.containerwidget( + parent=self._scrollwidget, + size=(self._scroll_width, self._scroll_height), + background=False, + ) + ba.textwidget( + parent=self._subcontainer, + color=(1, 1, 0, 1), + text=error, + maxwidth=self._scroll_width * 0.9, + position=( + self._scroll_width * 0.48, + self._scroll_height * 0.57, + ), + size=(0, 0), + h_align='center', + v_align='center', + ) else: file_names = [f for f in file_names if not f.startswith('.')] @@ -292,41 +339,53 @@ class FileSelectorWindow(ba.Window): entry_height = 35 folder_entry_height = 100 show_folder_entry = False - show_use_folder_button = (self._allow_folders - and not in_top_folder) + show_use_folder_button = self._allow_folders and not in_top_folder self._subcontainerheight = entry_height * len(entries) + ( - folder_entry_height if show_folder_entry else 0) - v = self._subcontainerheight - (folder_entry_height - if show_folder_entry else 0) + folder_entry_height if show_folder_entry else 0 + ) + v = self._subcontainerheight - ( + folder_entry_height if show_folder_entry else 0 + ) self._subcontainer = ba.containerwidget( parent=self._scrollwidget, size=(self._scroll_width, self._subcontainerheight), - background=False) + background=False, + ) - ba.containerwidget(edit=self._scrollwidget, - claims_left_right=False, - claims_tab=False) - ba.containerwidget(edit=self._subcontainer, - claims_left_right=False, - claims_tab=False, - selection_loops=False, - print_list_exit_instructions=False) + ba.containerwidget( + edit=self._scrollwidget, + claims_left_right=False, + claims_tab=False, + ) + ba.containerwidget( + edit=self._subcontainer, + claims_left_right=False, + claims_tab=False, + selection_loops=False, + print_list_exit_instructions=False, + ) ba.widget(edit=self._subcontainer, up_widget=self._back_button) if show_use_folder_button: self._use_folder_button = btn = ba.buttonwidget( parent=self._root_widget, - position=(self._width - self._button_width - 35 - - self._x_inset, self._height - 67), + position=( + self._width - self._button_width - 35 - self._x_inset, + self._height - 67, + ), size=(self._button_width, 50), - label=ba.Lstr(resource=self._r + - '.useThisFolderButtonText'), - on_activate_call=self._on_folder_entry_activated) - ba.widget(edit=btn, - left_widget=self._cancel_button, - down_widget=self._scrollwidget) + label=ba.Lstr( + resource=self._r + '.useThisFolderButtonText' + ), + on_activate_call=self._on_folder_entry_activated, + ) + ba.widget( + edit=btn, + left_widget=self._cancel_button, + down_widget=self._scrollwidget, + ) ba.widget(edit=self._cancel_button, right_widget=btn) ba.containerwidget(edit=self._root_widget, start_button=btn) @@ -339,46 +398,57 @@ class FileSelectorWindow(ba.Window): root_selectable=True, background=False, click_activate=True, - on_activate_call=ba.Call(self._on_entry_activated, entry)) + on_activate_call=ba.Call(self._on_entry_activated, entry), + ) if num == 0: ba.widget(edit=cnt, up_widget=self._back_button) is_valid_file_path = self._is_valid_file_path(entry) assert self._path is not None is_dir = os.path.isdir(self._path + '/' + entry) if is_dir: - ba.imagewidget(parent=cnt, - size=(folder_icon_size, folder_icon_size), - position=(10, 0.5 * entry_height - - folder_icon_size * 0.5), - draw_controller=cnt, - texture=self._folder_tex, - color=self._folder_color) + ba.imagewidget( + parent=cnt, + size=(folder_icon_size, folder_icon_size), + position=( + 10, + 0.5 * entry_height - folder_icon_size * 0.5, + ), + draw_controller=cnt, + texture=self._folder_tex, + color=self._folder_color, + ) else: - ba.imagewidget(parent=cnt, - size=(folder_icon_size, folder_icon_size), - position=(10, 0.5 * entry_height - - folder_icon_size * 0.5), - opacity=1.0 if is_valid_file_path else 0.5, - draw_controller=cnt, - texture=self._file_tex, - color=self._file_color) - ba.textwidget(parent=cnt, - draw_controller=cnt, - text=entry, - h_align='left', - v_align='center', - position=(10 + folder_icon_size * 1.05, - entry_height * 0.5), - size=(0, 0), - maxwidth=self._scroll_width * 0.93 - 50, - color=(1, 1, 1, 1) if - (is_valid_file_path or is_dir) else - (0.5, 0.5, 0.5, 1)) + ba.imagewidget( + parent=cnt, + size=(folder_icon_size, folder_icon_size), + position=( + 10, + 0.5 * entry_height - folder_icon_size * 0.5, + ), + opacity=1.0 if is_valid_file_path else 0.5, + draw_controller=cnt, + texture=self._file_tex, + color=self._file_color, + ) + ba.textwidget( + parent=cnt, + draw_controller=cnt, + text=entry, + h_align='left', + v_align='center', + position=(10 + folder_icon_size * 1.05, entry_height * 0.5), + size=(0, 0), + maxwidth=self._scroll_width * 0.93 - 50, + color=(1, 1, 1, 1) + if (is_valid_file_path or is_dir) + else (0.5, 0.5, 0.5, 1), + ) v -= entry_height def _is_valid_file_path(self, path: str) -> bool: - return any(path.lower().endswith(ext) - for ext in self._valid_file_extensions) + return any( + path.lower().endswith(ext) for ext in self._valid_file_extensions + ) def _cancel(self) -> None: ba.containerwidget(edit=self._root_widget, transition='out_right') diff --git a/dist/ba_data/python/bastd/ui/gather/__init__.py b/dist/ba_data/python/bastd/ui/gather/__init__.py index 7051393..0b3ed07 100644 --- a/dist/ba_data/python/bastd/ui/gather/__init__.py +++ b/dist/ba_data/python/bastd/ui/gather/__init__.py @@ -60,15 +60,18 @@ class GatherWindow(ba.Window): class TabID(Enum): """Our available tab types.""" + ABOUT = 'about' INTERNET = 'internet' PRIVATE = 'private' NEARBY = 'nearby' MANUAL = 'manual' - def __init__(self, - transition: str | None = 'in_right', - origin_widget: ba.Widget | None = None): + def __init__( + self, + transition: str | None = 'in_right', + origin_widget: ba.Widget | None = None, + ): # pylint: disable=too-many-statements # pylint: disable=too-many-locals # pylint: disable=cyclic-import @@ -92,25 +95,42 @@ class GatherWindow(ba.Window): uiscale = ba.app.ui.uiscale self._width = 1240 if uiscale is ba.UIScale.SMALL else 1040 x_offs = 100 if uiscale is ba.UIScale.SMALL else 0 - self._height = (582 if uiscale is ba.UIScale.SMALL else - 680 if uiscale is ba.UIScale.MEDIUM else 800) + self._height = ( + 582 + if uiscale is ba.UIScale.SMALL + else 680 + if uiscale is ba.UIScale.MEDIUM + else 800 + ) self._current_tab: GatherWindow.TabID | None = None extra_top = 20 if uiscale is ba.UIScale.SMALL else 0 self._r = 'gatherWindow' - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height + extra_top), - transition=transition, - toolbar_visibility='menu_minimal', - scale_origin_stack_offset=scale_origin, - scale=(1.3 if uiscale is ba.UIScale.SMALL else - 0.97 if uiscale is ba.UIScale.MEDIUM else 0.8), - stack_offset=(0, -11) if uiscale is ba.UIScale.SMALL else ( - 0, 0) if uiscale is ba.UIScale.MEDIUM else (0, 0))) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height + extra_top), + transition=transition, + toolbar_visibility='menu_minimal', + scale_origin_stack_offset=scale_origin, + scale=( + 1.3 + if uiscale is ba.UIScale.SMALL + else 0.97 + if uiscale is ba.UIScale.MEDIUM + else 0.8 + ), + stack_offset=(0, -11) + if uiscale is ba.UIScale.SMALL + else (0, 0) + if uiscale is ba.UIScale.MEDIUM + else (0, 0), + ) + ) if uiscale is ba.UIScale.SMALL and ba.app.ui.use_toolbars: - ba.containerwidget(edit=self._root_widget, - on_cancel_call=self._back) + ba.containerwidget( + edit=self._root_widget, on_cancel_call=self._back + ) self._back_button = None else: self._back_button = btn = ba.buttonwidget( @@ -121,56 +141,72 @@ class GatherWindow(ba.Window): autoselect=True, label=ba.Lstr(resource='backText'), button_type='back', - on_activate_call=self._back) + on_activate_call=self._back, + ) ba.containerwidget(edit=self._root_widget, cancel_button=btn) - ba.buttonwidget(edit=btn, - button_type='backSmall', - position=(70 + x_offs, self._height - 78), - size=(60, 60), - label=ba.charstr(ba.SpecialChar.BACK)) + ba.buttonwidget( + edit=btn, + button_type='backSmall', + position=(70 + x_offs, self._height - 78), + size=(60, 60), + label=ba.charstr(ba.SpecialChar.BACK), + ) condensed = uiscale is not ba.UIScale.LARGE - t_offs_y = (0 if not condensed else - 25 if uiscale is ba.UIScale.MEDIUM else 17) - ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, - self._height - 42 + t_offs_y), - size=(0, 0), - color=ba.app.ui.title_color, - scale=(1.5 if not condensed else - 1.0 if uiscale is ba.UIScale.MEDIUM else 0.6), - h_align='center', - v_align='center', - text=ba.Lstr(resource=self._r + '.titleText'), - maxwidth=550) + t_offs_y = ( + 0 if not condensed else 25 if uiscale is ba.UIScale.MEDIUM else 17 + ) + ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, self._height - 42 + t_offs_y), + size=(0, 0), + color=ba.app.ui.title_color, + scale=( + 1.5 + if not condensed + else 1.0 + if uiscale is ba.UIScale.MEDIUM + else 0.6 + ), + h_align='center', + v_align='center', + text=ba.Lstr(resource=self._r + '.titleText'), + maxwidth=550, + ) scroll_buffer_h = 130 + 2 * x_offs - tab_buffer_h = ((320 if condensed else 250) + 2 * x_offs) + tab_buffer_h = (320 if condensed else 250) + 2 * x_offs # Build up the set of tabs we want. tabdefs: list[tuple[GatherWindow.TabID, ba.Lstr]] = [ (self.TabID.ABOUT, ba.Lstr(resource=self._r + '.aboutText')) ] - if ba.internal.get_v1_account_misc_read_val('enablePublicParties', - True): - tabdefs.append((self.TabID.INTERNET, - ba.Lstr(resource=self._r + '.publicText'))) + if ba.internal.get_v1_account_misc_read_val( + 'enablePublicParties', True + ): + tabdefs.append( + (self.TabID.INTERNET, ba.Lstr(resource=self._r + '.publicText')) + ) tabdefs.append( - (self.TabID.PRIVATE, ba.Lstr(resource=self._r + '.privateText'))) + (self.TabID.PRIVATE, ba.Lstr(resource=self._r + '.privateText')) + ) tabdefs.append( - (self.TabID.NEARBY, ba.Lstr(resource=self._r + '.nearbyText'))) + (self.TabID.NEARBY, ba.Lstr(resource=self._r + '.nearbyText')) + ) tabdefs.append( - (self.TabID.MANUAL, ba.Lstr(resource=self._r + '.manualText'))) + (self.TabID.MANUAL, ba.Lstr(resource=self._r + '.manualText')) + ) # On small UI, push our tabs up closer to the top of the screen to # save a bit of space. tabs_top_extra = 42 if condensed else 0 - self._tab_row = TabRow(self._root_widget, - tabdefs, - pos=(tab_buffer_h * 0.5, - self._height - 130 + tabs_top_extra), - size=(self._width - tab_buffer_h, 50), - on_select_call=ba.WeakCall(self._set_tab)) + self._tab_row = TabRow( + self._root_widget, + tabdefs, + pos=(tab_buffer_h * 0.5, self._height - 130 + tabs_top_extra), + size=(self._width - tab_buffer_h, 50), + on_select_call=ba.WeakCall(self._set_tab), + ) # Now instantiate handlers for these tabs. tabtypes: dict[GatherWindow.TabID, type[GatherTab]] = { @@ -178,7 +214,7 @@ class GatherWindow(ba.Window): self.TabID.MANUAL: ManualGatherTab, self.TabID.PRIVATE: PrivateGatherTab, self.TabID.INTERNET: PublicGatherTab, - self.TabID.NEARBY: NearbyGatherTab + self.TabID.NEARBY: NearbyGatherTab, } self._tabs: dict[GatherWindow.TabID, GatherTab] = {} for tab_id in self._tab_row.tabs: @@ -189,29 +225,38 @@ class GatherWindow(ba.Window): if ba.app.ui.use_toolbars: ba.widget( edit=self._tab_row.tabs[tabdefs[-1][0]].button, - right_widget=ba.internal.get_special_widget('party_button')) + right_widget=ba.internal.get_special_widget('party_button'), + ) if uiscale is ba.UIScale.SMALL: ba.widget( edit=self._tab_row.tabs[tabdefs[0][0]].button, - left_widget=ba.internal.get_special_widget('back_button')) + left_widget=ba.internal.get_special_widget('back_button'), + ) self._scroll_width = self._width - scroll_buffer_h self._scroll_height = self._height - 180.0 + tabs_top_extra self._scroll_left = (self._width - self._scroll_width) * 0.5 - self._scroll_bottom = (self._height - self._scroll_height - 79 - 48 + - tabs_top_extra) + self._scroll_bottom = ( + self._height - self._scroll_height - 79 - 48 + tabs_top_extra + ) buffer_h = 10 buffer_v = 4 # Not actually using a scroll widget anymore; just an image. - ba.imagewidget(parent=self._root_widget, - position=(self._scroll_left - buffer_h, - self._scroll_bottom - buffer_v), - size=(self._scroll_width + 2 * buffer_h, - self._scroll_height + 2 * buffer_v), - texture=ba.gettexture('scrollWidget'), - model_transparent=ba.getmodel('softEdgeOutside')) + ba.imagewidget( + parent=self._root_widget, + position=( + self._scroll_left - buffer_h, + self._scroll_bottom - buffer_v, + ), + size=( + self._scroll_width + 2 * buffer_h, + self._scroll_height + 2 * buffer_v, + ), + texture=ba.gettexture('scrollWidget'), + model_transparent=ba.getmodel('softEdgeOutside'), + ) self._tab_container: ba.Widget | None = None self._restore_state() @@ -222,11 +267,13 @@ class GatherWindow(ba.Window): def playlist_select(self, origin_widget: ba.Widget) -> None: """Called by the private-hosting tab to select a playlist.""" from bastd.ui.play import PlayWindow + self._save_state() ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.ui.selecting_private_party_playlist = True ba.app.ui.set_main_menu_window( - PlayWindow(origin_widget=origin_widget).get_root_widget()) + PlayWindow(origin_widget=origin_widget).get_root_widget() + ) def _set_tab(self, tab_id: TabID) -> None: if self._current_tab is tab_id: @@ -270,7 +317,8 @@ class GatherWindow(ba.Window): sel = self._root_widget.get_selected_child() selected_tab_ids = [ - tab_id for tab_id, tab in self._tab_row.tabs.items() + tab_id + for tab_id, tab in self._tab_row.tabs.items() if sel == tab.button ] if sel == self._back_button: @@ -290,6 +338,7 @@ class GatherWindow(ba.Window): def _restore_state(self) -> None: from efro.util import enum_by_value + try: for tab in self._tabs.values(): tab.restore_state() @@ -313,8 +362,9 @@ class GatherWindow(ba.Window): sel = self._tab_container elif isinstance(sel_name, str) and sel_name.startswith('Tab:'): try: - sel_tab_id = enum_by_value(self.TabID, - sel_name.split(':')[-1]) + sel_tab_id = enum_by_value( + self.TabID, sel_name.split(':')[-1] + ) except ValueError: sel_tab_id = self.TabID.ABOUT sel = self._tab_row.tabs[sel_tab_id].button @@ -326,8 +376,11 @@ class GatherWindow(ba.Window): def _back(self) -> None: from bastd.ui.mainmenu import MainMenuWindow + self._save_state() - ba.containerwidget(edit=self._root_widget, - transition=self._transition_out) + ba.containerwidget( + edit=self._root_widget, transition=self._transition_out + ) ba.app.ui.set_main_menu_window( - MainMenuWindow(transition='in_left').get_root_widget()) + MainMenuWindow(transition='in_left').get_root_widget() + ) diff --git a/dist/ba_data/python/bastd/ui/gather/abouttab.py b/dist/ba_data/python/bastd/ui/gather/abouttab.py index 23dfcce..848032a 100644 --- a/dist/ba_data/python/bastd/ui/gather/abouttab.py +++ b/dist/ba_data/python/bastd/ui/gather/abouttab.py @@ -30,12 +30,17 @@ class AboutGatherTab(GatherTab): region_left: float, region_bottom: float, ) -> ba.Widget: - party_button_label = ('X' if ba.app.iircade_mode else ba.charstr( - ba.SpecialChar.TOP_BUTTON)) + party_button_label = ( + 'X' + if ba.app.iircade_mode + else ba.charstr(ba.SpecialChar.TOP_BUTTON) + ) message = ba.Lstr( resource='gatherWindow.aboutDescriptionText', - subs=[('${PARTY}', ba.charstr(ba.SpecialChar.PARTY_ICON)), - ('${BUTTON}', party_button_label)], + subs=[ + ('${PARTY}', ba.charstr(ba.SpecialChar.PARTY_ICON)), + ('${BUTTON}', party_button_label), + ], ) # Let's not talk about sharing in vr-mode; its tricky to fit more @@ -43,69 +48,90 @@ class AboutGatherTab(GatherTab): if not ba.app.vr_mode: message = ba.Lstr( value='${A}\n\n${B}', - subs=[('${A}', message), - ('${B}', - ba.Lstr(resource='gatherWindow.' - 'aboutDescriptionLocalMultiplayerExtraText'))]) + subs=[ + ('${A}', message), + ( + '${B}', + ba.Lstr( + resource='gatherWindow.' + 'aboutDescriptionLocalMultiplayerExtraText' + ), + ), + ], + ) string_height = 400 include_invite = True msc_scale = 1.1 c_height_2 = min(region_height, string_height * msc_scale + 100) try_tickets = ba.internal.get_v1_account_misc_read_val( - 'friendTryTickets', None) + 'friendTryTickets', None + ) if try_tickets is None: include_invite = False self._container = ba.containerwidget( parent=parent_widget, - position=(region_left, - region_bottom + (region_height - c_height_2) * 0.5), + position=( + region_left, + region_bottom + (region_height - c_height_2) * 0.5, + ), size=(region_width, c_height_2), background=False, - selectable=include_invite) + selectable=include_invite, + ) ba.widget(edit=self._container, up_widget=tab_button) - ba.textwidget(parent=self._container, - position=(region_width * 0.5, c_height_2 * - (0.58 if include_invite else 0.5)), - color=(0.6, 1.0, 0.6), - scale=msc_scale, - size=(0, 0), - maxwidth=region_width * 0.9, - max_height=c_height_2 * (0.7 if include_invite else 0.9), - h_align='center', - v_align='center', - text=message) + ba.textwidget( + parent=self._container, + position=( + region_width * 0.5, + c_height_2 * (0.58 if include_invite else 0.5), + ), + color=(0.6, 1.0, 0.6), + scale=msc_scale, + size=(0, 0), + maxwidth=region_width * 0.9, + max_height=c_height_2 * (0.7 if include_invite else 0.9), + h_align='center', + v_align='center', + text=message, + ) if include_invite: - ba.textwidget(parent=self._container, - position=(region_width * 0.57, 35), - color=(0, 1, 0), - scale=0.6, - size=(0, 0), - maxwidth=region_width * 0.5, - h_align='right', - v_align='center', - flatness=1.0, - text=ba.Lstr( - resource='gatherWindow.inviteAFriendText', - subs=[('${COUNT}', str(try_tickets))])) + ba.textwidget( + parent=self._container, + position=(region_width * 0.57, 35), + color=(0, 1, 0), + scale=0.6, + size=(0, 0), + maxwidth=region_width * 0.5, + h_align='right', + v_align='center', + flatness=1.0, + text=ba.Lstr( + resource='gatherWindow.inviteAFriendText', + subs=[('${COUNT}', str(try_tickets))], + ), + ) ba.buttonwidget( parent=self._container, position=(region_width * 0.59, 10), size=(230, 50), color=(0.54, 0.42, 0.56), textcolor=(0, 1, 0), - label=ba.Lstr(resource='gatherWindow.inviteFriendsText', - fallback_resource=( - 'gatherWindow.getFriendInviteCodeText')), + label=ba.Lstr( + resource='gatherWindow.inviteFriendsText', + fallback_resource='gatherWindow.getFriendInviteCodeText', + ), autoselect=True, on_activate_call=ba.WeakCall(self._invite_to_try_press), - up_widget=tab_button) + up_widget=tab_button, + ) return self._container def _invite_to_try_press(self) -> None: from bastd.ui.account import show_sign_in_prompt from bastd.ui.appinvite import handle_app_invites_press + if ba.internal.get_v1_account_state() != 'signed_in': show_sign_in_prompt() return diff --git a/dist/ba_data/python/bastd/ui/gather/manualtab.py b/dist/ba_data/python/bastd/ui/gather/manualtab.py index 0fa1a7b..960e6ee 100644 --- a/dist/ba_data/python/bastd/ui/gather/manualtab.py +++ b/dist/ba_data/python/bastd/ui/gather/manualtab.py @@ -1,6 +1,7 @@ # Released under the MIT License. See LICENSE for details. # """Defines the manual tab in the gather UI.""" +# pylint: disable=too-many-lines from __future__ import annotations @@ -19,20 +20,21 @@ if TYPE_CHECKING: from bastd.ui.gather import GatherWindow -def _safe_set_text(txt: ba.Widget | None, - val: str | ba.Lstr, - success: bool = True) -> None: +def _safe_set_text( + txt: ba.Widget | None, val: str | ba.Lstr, success: bool = True +) -> None: if txt: - ba.textwidget(edit=txt, - text=val, - color=(0, 1, 0) if success else (1, 1, 0)) + ba.textwidget( + edit=txt, text=val, color=(0, 1, 0) if success else (1, 1, 0) + ) class _HostLookupThread(threading.Thread): """Thread to fetch an addr.""" - def __init__(self, name: str, port: int, call: Callable[[str | None, int], - Any]): + def __init__( + self, name: str, port: int, call: Callable[[str | None, int], Any] + ): super().__init__() self._name = name self._port = port @@ -42,15 +44,18 @@ class _HostLookupThread(threading.Thread): result: str | None try: import socket + result = socket.gethostbyname(self._name) except Exception: result = None - ba.pushcall(lambda: self._call(result, self._port), - from_other_thread=True) + ba.pushcall( + lambda: self._call(result, self._port), from_other_thread=True + ) class SubTabType(Enum): """Available sub-tabs.""" + JOIN_BY_ADDRESS = 'join_by_address' FAVORITES = 'favorites' @@ -58,6 +63,7 @@ class SubTabType(Enum): @dataclass class State: """State saved/restored only while the app is running.""" + sub_tab: SubTabType = SubTabType.JOIN_BY_ADDRESS @@ -107,11 +113,14 @@ class ManualGatherTab(GatherTab): self._container = ba.containerwidget( parent=parent_widget, - position=(region_left, - region_bottom + (region_height - c_height) * 0.5), + position=( + region_left, + region_bottom + (region_height - c_height) * 0.5, + ), size=(c_width, c_height), background=False, - selection_loops_to_parent=True) + selection_loops_to_parent=True, + ) v = c_height - 30 self._join_by_address_text = ba.textwidget( parent=self._container, @@ -131,7 +140,8 @@ class ManualGatherTab(GatherTab): region_height, playsound=True, ), - text=ba.Lstr(resource='gatherWindow.manualJoinSectionText')) + text=ba.Lstr(resource='gatherWindow.manualJoinSectionText'), + ) self._favorites_text = ba.textwidget( parent=self._container, position=(c_width * 0.5 + 45, v - 13), @@ -150,14 +160,18 @@ class ManualGatherTab(GatherTab): region_height, playsound=True, ), - text=ba.Lstr(resource='gatherWindow.favoritesText')) + text=ba.Lstr(resource='gatherWindow.favoritesText'), + ) ba.widget(edit=self._join_by_address_text, up_widget=tab_button) - ba.widget(edit=self._favorites_text, - left_widget=self._join_by_address_text, - up_widget=tab_button) + ba.widget( + edit=self._favorites_text, + left_widget=self._join_by_address_text, + up_widget=tab_button, + ) ba.widget(edit=tab_button, down_widget=self._favorites_text) - ba.widget(edit=self._join_by_address_text, - right_widget=self._favorites_text) + ba.widget( + edit=self._join_by_address_text, right_widget=self._favorites_text + ) self._set_sub_tab(self._sub_tab, region_width, region_height) return self._container @@ -172,11 +186,13 @@ class ManualGatherTab(GatherTab): assert isinstance(state, State) self._sub_tab = state.sub_tab - def _set_sub_tab(self, - value: SubTabType, - region_width: float, - region_height: float, - playsound: bool = False) -> None: + def _set_sub_tab( + self, + value: SubTabType, + region_width: float, + region_height: float, + playsound: bool = False, + ) -> None: assert self._container if playsound: ba.playsound(ba.getsound('click01')) @@ -184,17 +200,24 @@ class ManualGatherTab(GatherTab): self._sub_tab = value active_color = (0.6, 1.0, 0.6) inactive_color = (0.5, 0.4, 0.5) - ba.textwidget(edit=self._join_by_address_text, - color=active_color if value is SubTabType.JOIN_BY_ADDRESS - else inactive_color) - ba.textwidget(edit=self._favorites_text, - color=active_color - if value is SubTabType.FAVORITES else inactive_color) + ba.textwidget( + edit=self._join_by_address_text, + color=active_color + if value is SubTabType.JOIN_BY_ADDRESS + else inactive_color, + ) + ba.textwidget( + edit=self._favorites_text, + color=active_color + if value is SubTabType.FAVORITES + else inactive_color, + ) # Clear anything existing in the old sub-tab. for widget in self._container.get_children(): if widget and widget not in { - self._favorites_text, self._join_by_address_text + self._favorites_text, + self._join_by_address_text, }: widget.delete() @@ -205,74 +228,80 @@ class ManualGatherTab(GatherTab): self._build_favorites_tab(region_height) # The old manual tab - def _build_join_by_address_tab(self, region_width: float, - region_height: float) -> None: + def _build_join_by_address_tab( + self, region_width: float, region_height: float + ) -> None: c_width = region_width c_height = region_height - 20 last_addr = ba.app.config.get('Last Manual Party Connect Address', '') v = c_height - 70 v -= 70 - ba.textwidget(parent=self._container, - position=(c_width * 0.5 - 260 - 50, v), - color=(0.6, 1.0, 0.6), - scale=1.0, - size=(0, 0), - maxwidth=130, - h_align='right', - v_align='center', - text=ba.Lstr(resource='gatherWindow.' - 'manualAddressText')) - txt = ba.textwidget(parent=self._container, - editable=True, - description=ba.Lstr(resource='gatherWindow.' - 'manualAddressText'), - position=(c_width * 0.5 - 240 - 50, v - 30), - text=last_addr, - autoselect=True, - v_align='center', - scale=1.0, - size=(420, 60)) + ba.textwidget( + parent=self._container, + position=(c_width * 0.5 - 260 - 50, v), + color=(0.6, 1.0, 0.6), + scale=1.0, + size=(0, 0), + maxwidth=130, + h_align='right', + v_align='center', + text=ba.Lstr(resource='gatherWindow.' 'manualAddressText'), + ) + txt = ba.textwidget( + parent=self._container, + editable=True, + description=ba.Lstr(resource='gatherWindow.' 'manualAddressText'), + position=(c_width * 0.5 - 240 - 50, v - 30), + text=last_addr, + autoselect=True, + v_align='center', + scale=1.0, + size=(420, 60), + ) ba.widget(edit=self._join_by_address_text, down_widget=txt) ba.widget(edit=self._favorites_text, down_widget=txt) - ba.textwidget(parent=self._container, - position=(c_width * 0.5 - 260 + 490, v), - color=(0.6, 1.0, 0.6), - scale=1.0, - size=(0, 0), - maxwidth=80, - h_align='right', - v_align='center', - text=ba.Lstr(resource='gatherWindow.' - 'portText')) - txt2 = ba.textwidget(parent=self._container, - editable=True, - description=ba.Lstr(resource='gatherWindow.' - 'portText'), - text='43210', - autoselect=True, - max_chars=5, - position=(c_width * 0.5 - 240 + 490, v - 30), - v_align='center', - scale=1.0, - size=(170, 60)) + ba.textwidget( + parent=self._container, + position=(c_width * 0.5 - 260 + 490, v), + color=(0.6, 1.0, 0.6), + scale=1.0, + size=(0, 0), + maxwidth=80, + h_align='right', + v_align='center', + text=ba.Lstr(resource='gatherWindow.' 'portText'), + ) + txt2 = ba.textwidget( + parent=self._container, + editable=True, + description=ba.Lstr(resource='gatherWindow.' 'portText'), + text='43210', + autoselect=True, + max_chars=5, + position=(c_width * 0.5 - 240 + 490, v - 30), + v_align='center', + scale=1.0, + size=(170, 60), + ) v -= 110 - btn = ba.buttonwidget(parent=self._container, - size=(300, 70), - label=ba.Lstr(resource='gatherWindow.' - 'manualConnectText'), - position=(c_width * 0.5 - 300, v), - autoselect=True, - on_activate_call=ba.Call(self._connect, txt, - txt2)) + btn = ba.buttonwidget( + parent=self._container, + size=(300, 70), + label=ba.Lstr(resource='gatherWindow.' 'manualConnectText'), + position=(c_width * 0.5 - 300, v), + autoselect=True, + on_activate_call=ba.Call(self._connect, txt, txt2), + ) savebutton = ba.buttonwidget( parent=self._container, size=(300, 70), label=ba.Lstr(resource='gatherWindow.favoritesSaveText'), position=(c_width * 0.5 - 240 + 490 - 200, v), autoselect=True, - on_activate_call=ba.Call(self._save_server, txt, txt2)) + on_activate_call=ba.Call(self._save_server, txt, txt2), + ) ba.widget(edit=btn, right_widget=savebutton) ba.widget(edit=savebutton, left_widget=btn, up_widget=txt2) ba.textwidget(edit=txt, on_return_press_call=btn.activate) @@ -282,8 +311,7 @@ class ManualGatherTab(GatherTab): self._check_button = ba.textwidget( parent=self._container, size=(250, 60), - text=ba.Lstr(resource='gatherWindow.' - 'showMyAddressText'), + text=ba.Lstr(resource='gatherWindow.' 'showMyAddressText'), v_align='center', h_align='center', click_activate=True, @@ -292,8 +320,13 @@ class ManualGatherTab(GatherTab): color=(0.5, 0.9, 0.5), scale=0.8, selectable=True, - on_activate_call=ba.Call(self._on_show_my_address_button_press, v, - self._container, c_width)) + on_activate_call=ba.Call( + self._on_show_my_address_button_press, + v, + self._container, + c_width, + ), + ) ba.widget(edit=self._check_button, up_widget=btn) # Tab containing saved favorite addresses @@ -305,8 +338,13 @@ class ManualGatherTab(GatherTab): uiscale = ba.app.ui.uiscale self._width = 1240 if uiscale is ba.UIScale.SMALL else 1040 x_inset = 100 if uiscale is ba.UIScale.SMALL else 0 - self._height = (578 if uiscale is ba.UIScale.SMALL else - 670 if uiscale is ba.UIScale.MEDIUM else 800) + self._height = ( + 578 + if uiscale is ba.UIScale.SMALL + else 670 + if uiscale is ba.UIScale.MEDIUM + else 800 + ) self._scroll_width = self._width - 130 + 2 * x_inset self._scroll_height = self._height - 180 @@ -315,19 +353,38 @@ class ManualGatherTab(GatherTab): c_height = self._scroll_height - 20 sub_scroll_height = c_height - 63 self._favorites_scroll_width = sub_scroll_width = ( - 680 if uiscale is ba.UIScale.SMALL else 640) + 680 if uiscale is ba.UIScale.SMALL else 640 + ) v = c_height - 30 b_width = 140 if uiscale is ba.UIScale.SMALL else 178 - b_height = (107 if uiscale is ba.UIScale.SMALL else - 142 if uiscale is ba.UIScale.MEDIUM else 190) - b_space_extra = (0 if uiscale is ba.UIScale.SMALL else - -2 if uiscale is ba.UIScale.MEDIUM else -5) + b_height = ( + 107 + if uiscale is ba.UIScale.SMALL + else 142 + if uiscale is ba.UIScale.MEDIUM + else 190 + ) + b_space_extra = ( + 0 + if uiscale is ba.UIScale.SMALL + else -2 + if uiscale is ba.UIScale.MEDIUM + else -5 + ) - btnv = (c_height - (48 if uiscale is ba.UIScale.SMALL else - 45 if uiscale is ba.UIScale.MEDIUM else 40) - - b_height) + btnv = ( + c_height + - ( + 48 + if uiscale is ba.UIScale.SMALL + else 45 + if uiscale is ba.UIScale.MEDIUM + else 40 + ) + - b_height + ) self._favorites_connect_button = btn1 = ba.buttonwidget( parent=self._container, @@ -339,56 +396,65 @@ class ManualGatherTab(GatherTab): on_activate_call=self._on_favorites_connect_press, text_scale=1.0 if uiscale is ba.UIScale.SMALL else 1.2, label=ba.Lstr(resource='gatherWindow.manualConnectText'), - autoselect=True) + autoselect=True, + ) if uiscale is ba.UIScale.SMALL and ba.app.ui.use_toolbars: ba.widget( edit=btn1, - left_widget=ba.internal.get_special_widget('back_button')) + left_widget=ba.internal.get_special_widget('back_button'), + ) btnv -= b_height + b_space_extra - ba.buttonwidget(parent=self._container, - size=(b_width, b_height), - position=(40 if uiscale is ba.UIScale.SMALL else 40, - btnv), - button_type='square', - color=(0.6, 0.53, 0.63), - textcolor=(0.75, 0.7, 0.8), - on_activate_call=self._on_favorites_edit_press, - text_scale=1.0 if uiscale is ba.UIScale.SMALL else 1.2, - label=ba.Lstr(resource='editText'), - autoselect=True) + ba.buttonwidget( + parent=self._container, + size=(b_width, b_height), + position=(40 if uiscale is ba.UIScale.SMALL else 40, btnv), + button_type='square', + color=(0.6, 0.53, 0.63), + textcolor=(0.75, 0.7, 0.8), + on_activate_call=self._on_favorites_edit_press, + text_scale=1.0 if uiscale is ba.UIScale.SMALL else 1.2, + label=ba.Lstr(resource='editText'), + autoselect=True, + ) btnv -= b_height + b_space_extra - ba.buttonwidget(parent=self._container, - size=(b_width, b_height), - position=(40 if uiscale is ba.UIScale.SMALL else 40, - btnv), - button_type='square', - color=(0.6, 0.53, 0.63), - textcolor=(0.75, 0.7, 0.8), - on_activate_call=self._on_favorite_delete_press, - text_scale=1.0 if uiscale is ba.UIScale.SMALL else 1.2, - label=ba.Lstr(resource='deleteText'), - autoselect=True) + ba.buttonwidget( + parent=self._container, + size=(b_width, b_height), + position=(40 if uiscale is ba.UIScale.SMALL else 40, btnv), + button_type='square', + color=(0.6, 0.53, 0.63), + textcolor=(0.75, 0.7, 0.8), + on_activate_call=self._on_favorite_delete_press, + text_scale=1.0 if uiscale is ba.UIScale.SMALL else 1.2, + label=ba.Lstr(resource='deleteText'), + autoselect=True, + ) v -= sub_scroll_height + 23 self._scrollwidget = scrlw = ba.scrollwidget( parent=self._container, position=(190 if uiscale is ba.UIScale.SMALL else 225, v), size=(sub_scroll_width, sub_scroll_height), - claims_left_right=True) - ba.widget(edit=self._favorites_connect_button, - right_widget=self._scrollwidget) - self._columnwidget = ba.columnwidget(parent=scrlw, - left_border=10, - border=2, - margin=0, - claims_left_right=True) + claims_left_right=True, + ) + ba.widget( + edit=self._favorites_connect_button, right_widget=self._scrollwidget + ) + self._columnwidget = ba.columnwidget( + parent=scrlw, + left_border=10, + border=2, + margin=0, + claims_left_right=True, + ) self._favorite_selected = None self._refresh_favorites() def _no_favorite_selected_error(self) -> None: - ba.screenmessage(ba.Lstr(resource='nothingIsSelectedErrorText'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr(resource='nothingIsSelectedErrorText'), color=(1, 0, 0) + ) ba.playsound(ba.getsound('error')) def _on_favorites_connect_press(self) -> None: @@ -397,10 +463,11 @@ class ManualGatherTab(GatherTab): else: config = ba.app.config['Saved Servers'][self._favorite_selected] - _HostLookupThread(name=config['addr'], - port=config['port'], - call=ba.WeakCall( - self._host_lookup_result)).start() + _HostLookupThread( + name=config['addr'], + port=config['port'], + call=ba.WeakCall(self._host_lookup_result), + ).start() def _on_favorites_edit_press(self) -> None: if self._favorite_selected is None: @@ -411,108 +478,130 @@ class ManualGatherTab(GatherTab): c_height = 310 uiscale = ba.app.ui.uiscale self._favorite_edit_window = cnt = ba.containerwidget( - scale=(1.8 if uiscale is ba.UIScale.SMALL else - 1.55 if uiscale is ba.UIScale.MEDIUM else 1.0), + scale=( + 1.8 + if uiscale is ba.UIScale.SMALL + else 1.55 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), size=(c_width, c_height), - transition='in_scale') + transition='in_scale', + ) - ba.textwidget(parent=cnt, - size=(0, 0), - h_align='center', - v_align='center', - text=ba.Lstr(resource='editText'), - color=(0.6, 1.0, 0.6), - maxwidth=c_width * 0.8, - position=(c_width * 0.5, c_height - 60)) + ba.textwidget( + parent=cnt, + size=(0, 0), + h_align='center', + v_align='center', + text=ba.Lstr(resource='editText'), + color=(0.6, 1.0, 0.6), + maxwidth=c_width * 0.8, + position=(c_width * 0.5, c_height - 60), + ) - ba.textwidget(parent=cnt, - position=(c_width * 0.2 - 15, c_height - 120), - color=(0.6, 1.0, 0.6), - scale=1.0, - size=(0, 0), - maxwidth=60, - h_align='right', - v_align='center', - text=ba.Lstr(resource='nameText')) + ba.textwidget( + parent=cnt, + position=(c_width * 0.2 - 15, c_height - 120), + color=(0.6, 1.0, 0.6), + scale=1.0, + size=(0, 0), + maxwidth=60, + h_align='right', + v_align='center', + text=ba.Lstr(resource='nameText'), + ) self._party_edit_name_text = ba.textwidget( parent=cnt, size=(c_width * 0.7, 40), h_align='left', v_align='center', - text=ba.app.config['Saved Servers'][ - self._favorite_selected]['name'], + text=ba.app.config['Saved Servers'][self._favorite_selected][ + 'name' + ], editable=True, description=ba.Lstr(resource='nameText'), position=(c_width * 0.2, c_height - 140), autoselect=True, maxwidth=c_width * 0.6, - max_chars=200) + max_chars=200, + ) - ba.textwidget(parent=cnt, - position=(c_width * 0.2 - 15, c_height - 180), - color=(0.6, 1.0, 0.6), - scale=1.0, - size=(0, 0), - maxwidth=60, - h_align='right', - v_align='center', - text=ba.Lstr(resource='gatherWindow.' - 'manualAddressText')) + ba.textwidget( + parent=cnt, + position=(c_width * 0.2 - 15, c_height - 180), + color=(0.6, 1.0, 0.6), + scale=1.0, + size=(0, 0), + maxwidth=60, + h_align='right', + v_align='center', + text=ba.Lstr(resource='gatherWindow.' 'manualAddressText'), + ) self._party_edit_addr_text = ba.textwidget( parent=cnt, size=(c_width * 0.4, 40), h_align='left', v_align='center', - text=ba.app.config['Saved Servers'][ - self._favorite_selected]['addr'], + text=ba.app.config['Saved Servers'][self._favorite_selected][ + 'addr' + ], editable=True, description=ba.Lstr(resource='gatherWindow.manualAddressText'), position=(c_width * 0.2, c_height - 200), autoselect=True, maxwidth=c_width * 0.35, - max_chars=200) + max_chars=200, + ) - ba.textwidget(parent=cnt, - position=(c_width * 0.7 - 10, c_height - 180), - color=(0.6, 1.0, 0.6), - scale=1.0, - size=(0, 0), - maxwidth=45, - h_align='right', - v_align='center', - text=ba.Lstr(resource='gatherWindow.' - 'portText')) + ba.textwidget( + parent=cnt, + position=(c_width * 0.7 - 10, c_height - 180), + color=(0.6, 1.0, 0.6), + scale=1.0, + size=(0, 0), + maxwidth=45, + h_align='right', + v_align='center', + text=ba.Lstr(resource='gatherWindow.' 'portText'), + ) self._party_edit_port_text = ba.textwidget( parent=cnt, size=(c_width * 0.2, 40), h_align='left', v_align='center', - text=str(ba.app.config['Saved Servers'][self._favorite_selected] - ['port']), + text=str( + ba.app.config['Saved Servers'][self._favorite_selected]['port'] + ), editable=True, description=ba.Lstr(resource='gatherWindow.portText'), position=(c_width * 0.7, c_height - 200), autoselect=True, maxwidth=c_width * 0.2, - max_chars=6) + max_chars=6, + ) cbtn = ba.buttonwidget( parent=cnt, label=ba.Lstr(resource='cancelText'), on_activate_call=ba.Call( lambda c: ba.containerwidget(edit=c, transition='out_scale'), - cnt), + cnt, + ), size=(180, 60), position=(30, 30), - autoselect=True) - okb = ba.buttonwidget(parent=cnt, - label=ba.Lstr(resource='saveText'), - size=(180, 60), - position=(c_width - 230, 30), - on_activate_call=ba.Call(self._edit_saved_party), - autoselect=True) + autoselect=True, + ) + okb = ba.buttonwidget( + parent=cnt, + label=ba.Lstr(resource='saveText'), + size=(180, 60), + position=(c_width - 230, 30), + on_activate_call=ba.Call(self._edit_saved_party), + autoselect=True, + ) ba.widget(edit=cbtn, right_widget=okb) ba.widget(edit=okb, left_widget=cbtn) ba.containerwidget(edit=cnt, cancel_button=cbtn, start_button=okb) @@ -524,12 +613,15 @@ class ManualGatherTab(GatherTab): return if not self._party_edit_name_text or not self._party_edit_addr_text: return - new_name_raw = cast(str, - ba.textwidget(query=self._party_edit_name_text)) - new_addr_raw = cast(str, - ba.textwidget(query=self._party_edit_addr_text)) - new_port_raw = cast(str, - ba.textwidget(query=self._party_edit_port_text)) + new_name_raw = cast( + str, ba.textwidget(query=self._party_edit_name_text) + ) + new_addr_raw = cast( + str, ba.textwidget(query=self._party_edit_addr_text) + ) + new_port_raw = cast( + str, ba.textwidget(query=self._party_edit_port_text) + ) ba.app.config['Saved Servers'][server]['name'] = new_name_raw ba.app.config['Saved Servers'][server]['addr'] = new_addr_raw try: @@ -541,19 +633,32 @@ class ManualGatherTab(GatherTab): ba.playsound(ba.getsound('gunCocking')) self._refresh_favorites() - ba.containerwidget(edit=self._favorite_edit_window, - transition='out_scale') + ba.containerwidget( + edit=self._favorite_edit_window, transition='out_scale' + ) def _on_favorite_delete_press(self) -> None: from bastd.ui import confirm + if self._favorite_selected is None: self._no_favorite_selected_error() return confirm.ConfirmWindow( - ba.Lstr(resource='gameListWindow.deleteConfirmText', - subs=[('${LIST}', ba.app.config['Saved Servers'][ - self._favorite_selected]['name'])]), - self._delete_saved_party, 450, 150) + ba.Lstr( + resource='gameListWindow.deleteConfirmText', + subs=[ + ( + '${LIST}', + ba.app.config['Saved Servers'][self._favorite_selected][ + 'name' + ], + ) + ], + ), + self._delete_saved_party, + 450, + 150, + ) def _delete_saved_party(self) -> None: if self._favorite_selected is None: @@ -593,38 +698,50 @@ class ManualGatherTab(GatherTab): always_highlight=True, on_select_call=ba.Call(self._on_favorite_select, server), on_activate_call=self._favorites_connect_button.activate, - text=(config['Saved Servers'][server]['name'] - if config['Saved Servers'][server]['name'] != '' else - config['Saved Servers'][server]['addr'] + ' ' + - str(config['Saved Servers'][server]['port'])), + text=( + config['Saved Servers'][server]['name'] + if config['Saved Servers'][server]['name'] != '' + else config['Saved Servers'][server]['addr'] + + ' ' + + str(config['Saved Servers'][server]['port']) + ), h_align='left', v_align='center', corner_scale=t_scale, - maxwidth=(self._favorites_scroll_width / t_scale) * 0.93) + maxwidth=(self._favorites_scroll_width / t_scale) * 0.93, + ) if i == 0: ba.widget(edit=txt, up_widget=self._favorites_text) - ba.widget(edit=txt, - left_widget=self._favorites_connect_button, - right_widget=txt) + ba.widget( + edit=txt, + left_widget=self._favorites_connect_button, + right_widget=txt, + ) # If there's no servers, allow selecting out of the scroll area - ba.containerwidget(edit=self._scrollwidget, - claims_left_right=bool(servers), - claims_up_down=bool(servers)) - ba.widget(edit=self._scrollwidget, - up_widget=self._favorites_text, - left_widget=self._favorites_connect_button) + ba.containerwidget( + edit=self._scrollwidget, + claims_left_right=bool(servers), + claims_up_down=bool(servers), + ) + ba.widget( + edit=self._scrollwidget, + up_widget=self._favorites_text, + left_widget=self._favorites_connect_button, + ) def on_deactivate(self) -> None: self._access_check_timer = None - def _connect(self, textwidget: ba.Widget, - port_textwidget: ba.Widget) -> None: + def _connect( + self, textwidget: ba.Widget, port_textwidget: ba.Widget + ) -> None: addr = cast(str, ba.textwidget(query=textwidget)) if addr == '': ba.screenmessage( ba.Lstr(resource='internal.invalidAddressErrorText'), - color=(1, 0, 0)) + color=(1, 0, 0), + ) ba.playsound(ba.getsound('error')) return try: @@ -632,22 +749,26 @@ class ManualGatherTab(GatherTab): except ValueError: port = -1 if port > 65535 or port < 0: - ba.screenmessage(ba.Lstr(resource='internal.invalidPortErrorText'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr(resource='internal.invalidPortErrorText'), + color=(1, 0, 0), + ) ba.playsound(ba.getsound('error')) return - _HostLookupThread(name=addr, - port=port, - call=ba.WeakCall(self._host_lookup_result)).start() + _HostLookupThread( + name=addr, port=port, call=ba.WeakCall(self._host_lookup_result) + ).start() - def _save_server(self, textwidget: ba.Widget, - port_textwidget: ba.Widget) -> None: + def _save_server( + self, textwidget: ba.Widget, port_textwidget: ba.Widget + ) -> None: addr = cast(str, ba.textwidget(query=textwidget)) if addr == '': ba.screenmessage( ba.Lstr(resource='internal.invalidAddressErrorText'), - color=(1, 0, 0)) + color=(1, 0, 0), + ) ba.playsound(ba.getsound('error')) return try: @@ -655,8 +776,10 @@ class ManualGatherTab(GatherTab): except ValueError: port = -1 if port > 65535 or port < 0: - ba.screenmessage(ba.Lstr(resource='internal.invalidPortErrorText'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr(resource='internal.invalidPortErrorText'), + color=(1, 0, 0), + ) ba.playsound(ba.getsound('error')) return config = ba.app.config @@ -667,7 +790,7 @@ class ManualGatherTab(GatherTab): config['Saved Servers'][f'{addr}@{port}'] = { 'addr': addr, 'port': port, - 'name': addr + 'name': addr, } config.commit() ba.playsound(ba.getsound('gunCocking')) @@ -675,12 +798,14 @@ class ManualGatherTab(GatherTab): ba.screenmessage('Invalid Address', color=(1, 0, 0)) ba.playsound(ba.getsound('error')) - def _host_lookup_result(self, resolved_address: str | None, - port: int) -> None: + def _host_lookup_result( + self, resolved_address: str | None, port: int + ) -> None: if resolved_address is None: ba.screenmessage( ba.Lstr(resource='internal.unableToResolveHostText'), - color=(1, 0, 0)) + color=(1, 0, 0), + ) ba.playsound(ba.getsound('error')) else: # Store for later. @@ -693,6 +818,7 @@ class ManualGatherTab(GatherTab): try: # FIXME: Update this to work with IPv6. import socket + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.connect(('8.8.8.8', 80)) val = sock.getsockname()[0] @@ -707,25 +833,39 @@ class ManualGatherTab(GatherTab): ) except Exception as exc: from efro.error import is_udp_communication_error - if is_udp_communication_error(exc): - ba.pushcall(ba.Call( - _safe_set_text, self._checking_state_text, - ba.Lstr(resource='gatherWindow.' - 'noConnectionText'), False), - from_other_thread=True) - else: - ba.pushcall(ba.Call( - _safe_set_text, self._checking_state_text, - ba.Lstr(resource='gatherWindow.' - 'addressFetchErrorText'), False), - from_other_thread=True) - ba.pushcall(ba.Call(ba.print_error, - 'error in AddrFetchThread: ' + str(exc)), - from_other_thread=True) - def _on_show_my_address_button_press(self, v2: float, - container: ba.Widget | None, - c_width: float) -> None: + if is_udp_communication_error(exc): + ba.pushcall( + ba.Call( + _safe_set_text, + self._checking_state_text, + ba.Lstr(resource='gatherWindow.' 'noConnectionText'), + False, + ), + from_other_thread=True, + ) + else: + ba.pushcall( + ba.Call( + _safe_set_text, + self._checking_state_text, + ba.Lstr( + resource='gatherWindow.' 'addressFetchErrorText' + ), + False, + ), + from_other_thread=True, + ) + ba.pushcall( + ba.Call( + ba.print_error, 'error in AddrFetchThread: ' + str(exc) + ), + from_other_thread=True, + ) + + def _on_show_my_address_button_press( + self, v2: float, container: ba.Widget | None, c_width: float + ) -> None: if not container: return @@ -733,17 +873,18 @@ class ManualGatherTab(GatherTab): tspc = 25 ba.playsound(ba.getsound('swish')) - ba.textwidget(parent=container, - position=(c_width * 0.5 - 10, v2), - color=(0.6, 1.0, 0.6), - scale=tscl, - size=(0, 0), - maxwidth=c_width * 0.45, - flatness=1.0, - h_align='right', - v_align='center', - text=ba.Lstr(resource='gatherWindow.' - 'manualYourLocalAddressText')) + ba.textwidget( + parent=container, + position=(c_width * 0.5 - 10, v2), + color=(0.6, 1.0, 0.6), + scale=tscl, + size=(0, 0), + maxwidth=c_width * 0.45, + flatness=1.0, + h_align='right', + v_align='center', + text=ba.Lstr(resource='gatherWindow.' 'manualYourLocalAddressText'), + ) self._checking_state_text = ba.textwidget( parent=container, position=(c_width * 0.5, v2), @@ -754,87 +895,106 @@ class ManualGatherTab(GatherTab): flatness=1.0, h_align='left', v_align='center', - text=ba.Lstr(resource='gatherWindow.' - 'checkingText')) + text=ba.Lstr(resource='gatherWindow.' 'checkingText'), + ) threading.Thread(target=self._run_addr_fetch).start() v2 -= tspc - ba.textwidget(parent=container, - position=(c_width * 0.5 - 10, v2), - color=(0.6, 1.0, 0.6), - scale=tscl, - size=(0, 0), - maxwidth=c_width * 0.45, - flatness=1.0, - h_align='right', - v_align='center', - text=ba.Lstr(resource='gatherWindow.' - 'manualYourAddressFromInternetText')) + ba.textwidget( + parent=container, + position=(c_width * 0.5 - 10, v2), + color=(0.6, 1.0, 0.6), + scale=tscl, + size=(0, 0), + maxwidth=c_width * 0.45, + flatness=1.0, + h_align='right', + v_align='center', + text=ba.Lstr( + resource='gatherWindow.' 'manualYourAddressFromInternetText' + ), + ) - t_addr = ba.textwidget(parent=container, - position=(c_width * 0.5, v2), - color=(0.5, 0.5, 0.5), - scale=tscl, - size=(0, 0), - maxwidth=c_width * 0.45, - h_align='left', - v_align='center', - flatness=1.0, - text=ba.Lstr(resource='gatherWindow.' - 'checkingText')) + t_addr = ba.textwidget( + parent=container, + position=(c_width * 0.5, v2), + color=(0.5, 0.5, 0.5), + scale=tscl, + size=(0, 0), + maxwidth=c_width * 0.45, + h_align='left', + v_align='center', + flatness=1.0, + text=ba.Lstr(resource='gatherWindow.' 'checkingText'), + ) v2 -= tspc - ba.textwidget(parent=container, - position=(c_width * 0.5 - 10, v2), - color=(0.6, 1.0, 0.6), - scale=tscl, - size=(0, 0), - maxwidth=c_width * 0.45, - flatness=1.0, - h_align='right', - v_align='center', - text=ba.Lstr(resource='gatherWindow.' - 'manualJoinableFromInternetText')) + ba.textwidget( + parent=container, + position=(c_width * 0.5 - 10, v2), + color=(0.6, 1.0, 0.6), + scale=tscl, + size=(0, 0), + maxwidth=c_width * 0.45, + flatness=1.0, + h_align='right', + v_align='center', + text=ba.Lstr( + resource='gatherWindow.' 'manualJoinableFromInternetText' + ), + ) - t_accessible = ba.textwidget(parent=container, - position=(c_width * 0.5, v2), - color=(0.5, 0.5, 0.5), - scale=tscl, - size=(0, 0), - maxwidth=c_width * 0.45, - flatness=1.0, - h_align='left', - v_align='center', - text=ba.Lstr(resource='gatherWindow.' - 'checkingText')) + t_accessible = ba.textwidget( + parent=container, + position=(c_width * 0.5, v2), + color=(0.5, 0.5, 0.5), + scale=tscl, + size=(0, 0), + maxwidth=c_width * 0.45, + flatness=1.0, + h_align='left', + v_align='center', + text=ba.Lstr(resource='gatherWindow.' 'checkingText'), + ) v2 -= 28 - t_accessible_extra = ba.textwidget(parent=container, - position=(c_width * 0.5, v2), - color=(1, 0.5, 0.2), - scale=0.7, - size=(0, 0), - maxwidth=c_width * 0.9, - flatness=1.0, - h_align='center', - v_align='center', - text='') + t_accessible_extra = ba.textwidget( + parent=container, + position=(c_width * 0.5, v2), + color=(1, 0.5, 0.2), + scale=0.7, + size=(0, 0), + maxwidth=c_width * 0.9, + flatness=1.0, + h_align='center', + v_align='center', + text='', + ) self._doing_access_check = False self._access_check_count = 0 # Cap our refreshes eventually. self._access_check_timer = ba.Timer( 10.0, - ba.WeakCall(self._access_check_update, t_addr, t_accessible, - t_accessible_extra), + ba.WeakCall( + self._access_check_update, + t_addr, + t_accessible, + t_accessible_extra, + ), repeat=True, - timetype=ba.TimeType.REAL) + timetype=ba.TimeType.REAL, + ) # Kick initial off. self._access_check_update(t_addr, t_accessible, t_accessible_extra) if self._check_button: self._check_button.delete() - def _access_check_update(self, t_addr: ba.Widget, t_accessible: ba.Widget, - t_accessible_extra: ba.Widget) -> None: + def _access_check_update( + self, + t_addr: ba.Widget, + t_accessible: ba.Widget, + t_accessible_extra: ba.Widget, + ) -> None: from ba.internal import master_server_get # If we don't have an outstanding query, start one.. @@ -846,9 +1006,11 @@ class ManualGatherTab(GatherTab): self._t_addr = t_addr self._t_accessible = t_accessible self._t_accessible_extra = t_accessible_extra - master_server_get('bsAccessCheck', {'b': ba.app.build_number}, - callback=ba.WeakCall( - self._on_accessible_response)) + master_server_get( + 'bsAccessCheck', + {'b': ba.app.build_number}, + callback=ba.WeakCall(self._on_accessible_response), + ) def _on_accessible_response(self, data: dict[str, Any] | None) -> None: t_addr = self._t_addr @@ -859,37 +1021,42 @@ class ManualGatherTab(GatherTab): color_good = (0, 1, 0) if data is None or 'address' not in data or 'accessible' not in data: if t_addr: - ba.textwidget(edit=t_addr, - text=ba.Lstr(resource='gatherWindow.' - 'noConnectionText'), - color=color_bad) + ba.textwidget( + edit=t_addr, + text=ba.Lstr(resource='gatherWindow.' 'noConnectionText'), + color=color_bad, + ) if t_accessible: - ba.textwidget(edit=t_accessible, - text=ba.Lstr(resource='gatherWindow.' - 'noConnectionText'), - color=color_bad) + ba.textwidget( + edit=t_accessible, + text=ba.Lstr(resource='gatherWindow.' 'noConnectionText'), + color=color_bad, + ) if t_accessible_extra: - ba.textwidget(edit=t_accessible_extra, - text='', - color=color_bad) + ba.textwidget(edit=t_accessible_extra, text='', color=color_bad) return if t_addr: ba.textwidget(edit=t_addr, text=data['address'], color=color_good) if t_accessible: if data['accessible']: - ba.textwidget(edit=t_accessible, - text=ba.Lstr(resource='gatherWindow.' - 'manualJoinableYesText'), - color=color_good) + ba.textwidget( + edit=t_accessible, + text=ba.Lstr( + resource='gatherWindow.' 'manualJoinableYesText' + ), + color=color_good, + ) if t_accessible_extra: - ba.textwidget(edit=t_accessible_extra, - text='', - color=color_good) + ba.textwidget( + edit=t_accessible_extra, text='', color=color_good + ) else: ba.textwidget( edit=t_accessible, - text=ba.Lstr(resource='gatherWindow.' - 'manualJoinableNoWithAsteriskText'), + text=ba.Lstr( + resource='gatherWindow.' + 'manualJoinableNoWithAsteriskText' + ), color=color_bad, ) if t_accessible_extra: diff --git a/dist/ba_data/python/bastd/ui/gather/nearbytab.py b/dist/ba_data/python/bastd/ui/gather/nearbytab.py index c5ebcdd..16f68b3 100644 --- a/dist/ba_data/python/bastd/ui/gather/nearbytab.py +++ b/dist/ba_data/python/bastd/ui/gather/nearbytab.py @@ -19,23 +19,29 @@ if TYPE_CHECKING: class NetScanner: """Class for scanning for games on the lan.""" - def __init__(self, tab: GatherTab, scrollwidget: ba.Widget, - tab_button: ba.Widget, width: float): + def __init__( + self, + tab: GatherTab, + scrollwidget: ba.Widget, + tab_button: ba.Widget, + width: float, + ): self._tab = weakref.ref(tab) self._scrollwidget = scrollwidget self._tab_button = tab_button - self._columnwidget = ba.columnwidget(parent=self._scrollwidget, - border=2, - margin=0, - left_border=10) + self._columnwidget = ba.columnwidget( + parent=self._scrollwidget, border=2, margin=0, left_border=10 + ) ba.widget(edit=self._columnwidget, up_widget=tab_button) self._width = width self._last_selected_host: dict[str, Any] | None = None - self._update_timer = ba.Timer(1.0, - ba.WeakCall(self.update), - timetype=ba.TimeType.REAL, - repeat=True) + self._update_timer = ba.Timer( + 1.0, + ba.WeakCall(self.update), + timetype=ba.TimeType.REAL, + repeat=True, + ) # Go ahead and run a few *almost* immediately so we don't # have to wait a second. self.update() @@ -55,8 +61,10 @@ class NetScanner: # In case our UI was killed from under us. if not self._columnwidget: - print(f'ERROR: NetScanner running without UI at time' - f' {ba.time(timetype=ba.TimeType.REAL)}.') + print( + f'ERROR: NetScanner running without UI at time' + f' {ba.time(timetype=ba.TimeType.REAL)}.' + ) return t_scale = 1.6 @@ -67,23 +75,26 @@ class NetScanner: last_selected_host = self._last_selected_host hosts = ba.internal.host_scan_cycle() for i, host in enumerate(hosts): - txt3 = ba.textwidget(parent=self._columnwidget, - size=(self._width / t_scale, 30), - selectable=True, - color=(1, 1, 1), - on_select_call=ba.Call(self._on_select, host), - on_activate_call=ba.Call( - self._on_activate, host), - click_activate=True, - text=host['display_string'], - h_align='left', - v_align='center', - corner_scale=t_scale, - maxwidth=(self._width / t_scale) * 0.93) + txt3 = ba.textwidget( + parent=self._columnwidget, + size=(self._width / t_scale, 30), + selectable=True, + color=(1, 1, 1), + on_select_call=ba.Call(self._on_select, host), + on_activate_call=ba.Call(self._on_activate, host), + click_activate=True, + text=host['display_string'], + h_align='left', + v_align='center', + corner_scale=t_scale, + maxwidth=(self._width / t_scale) * 0.93, + ) if host == last_selected_host: - ba.containerwidget(edit=self._columnwidget, - selected_child=txt3, - visible_child=txt3) + ba.containerwidget( + edit=self._columnwidget, + selected_child=txt3, + visible_child=txt3, + ) if i == 0: ba.widget(edit=txt3, up_widget=self._tab_button) @@ -111,33 +122,39 @@ class NearbyGatherTab(GatherTab): sub_scroll_width = 650 self._container = ba.containerwidget( parent=parent_widget, - position=(region_left, - region_bottom + (region_height - c_height) * 0.5), + position=( + region_left, + region_bottom + (region_height - c_height) * 0.5, + ), size=(c_width, c_height), background=False, - selection_loops_to_parent=True) + selection_loops_to_parent=True, + ) v = c_height - 30 - ba.textwidget(parent=self._container, - position=(c_width * 0.5, v - 3), - color=(0.6, 1.0, 0.6), - scale=1.3, - size=(0, 0), - maxwidth=c_width * 0.9, - h_align='center', - v_align='center', - text=ba.Lstr(resource='gatherWindow.' - 'localNetworkDescriptionText')) + ba.textwidget( + parent=self._container, + position=(c_width * 0.5, v - 3), + color=(0.6, 1.0, 0.6), + scale=1.3, + size=(0, 0), + maxwidth=c_width * 0.9, + h_align='center', + v_align='center', + text=ba.Lstr( + resource='gatherWindow.' 'localNetworkDescriptionText' + ), + ) v -= 15 v -= sub_scroll_height + 23 - scrollw = ba.scrollwidget(parent=self._container, - position=((region_width - sub_scroll_width) * - 0.5, v), - size=(sub_scroll_width, sub_scroll_height)) + scrollw = ba.scrollwidget( + parent=self._container, + position=((region_width - sub_scroll_width) * 0.5, v), + size=(sub_scroll_width, sub_scroll_height), + ) - self._net_scanner = NetScanner(self, - scrollw, - tab_button, - width=sub_scroll_width) + self._net_scanner = NetScanner( + self, scrollw, tab_button, width=sub_scroll_width + ) ba.widget(edit=scrollw, autoselect=True, up_widget=tab_button) return self._container diff --git a/dist/ba_data/python/bastd/ui/gather/privatetab.py b/dist/ba_data/python/bastd/ui/gather/privatetab.py index 3f7dcf1..88ed22b 100644 --- a/dist/ba_data/python/bastd/ui/gather/privatetab.py +++ b/dist/ba_data/python/bastd/ui/gather/privatetab.py @@ -12,8 +12,11 @@ from dataclasses import dataclass from typing import TYPE_CHECKING, cast from efro.dataclassio import dataclass_from_dict, dataclass_to_dict -from bacommon.net import (PrivateHostingState, PrivateHostingConfig, - PrivatePartyConnectResult) +from bacommon.net import ( + PrivateHostingState, + PrivateHostingConfig, + PrivatePartyConnectResult, +) import ba import ba.internal from bastd.ui.gather import GatherTab @@ -29,6 +32,7 @@ DEBUG_SERVER_COMMUNICATION = os.environ.get('BA_DEBUG_PPTABCOM') == '1' class SubTabType(Enum): """Available sub-tabs.""" + JOIN = 'join' HOST = 'host' @@ -36,6 +40,7 @@ class SubTabType(Enum): @dataclass class State: """Our core state that persists while the app is running.""" + sub_tab: SubTabType = SubTabType.JOIN @@ -85,11 +90,14 @@ class PrivateGatherTab(GatherTab): self._c_height = region_height - 20 self._container = ba.containerwidget( parent=parent_widget, - position=(region_left, - region_bottom + (region_height - self._c_height) * 0.5), + position=( + region_left, + region_bottom + (region_height - self._c_height) * 0.5, + ), size=(self._c_width, self._c_height), background=False, - selection_loops_to_parent=True) + selection_loops_to_parent=True, + ) v = self._c_height - 30.0 self._join_sub_tab_text = ba.textwidget( parent=self._container, @@ -107,7 +115,8 @@ class PrivateGatherTab(GatherTab): SubTabType.JOIN, playsound=True, ), - text=ba.Lstr(resource='gatherWindow.privatePartyJoinText')) + text=ba.Lstr(resource='gatherWindow.privatePartyJoinText'), + ) self._host_sub_tab_text = ba.textwidget( parent=self._container, position=(self._c_width * 0.5 + 45, v - 13), @@ -124,18 +133,24 @@ class PrivateGatherTab(GatherTab): SubTabType.HOST, playsound=True, ), - text=ba.Lstr(resource='gatherWindow.privatePartyHostText')) + text=ba.Lstr(resource='gatherWindow.privatePartyHostText'), + ) ba.widget(edit=self._join_sub_tab_text, up_widget=tab_button) - ba.widget(edit=self._host_sub_tab_text, - left_widget=self._join_sub_tab_text, - up_widget=tab_button) - ba.widget(edit=self._join_sub_tab_text, - right_widget=self._host_sub_tab_text) + ba.widget( + edit=self._host_sub_tab_text, + left_widget=self._join_sub_tab_text, + up_widget=tab_button, + ) + ba.widget( + edit=self._join_sub_tab_text, right_widget=self._host_sub_tab_text + ) - self._update_timer = ba.Timer(1.0, - ba.WeakCall(self._update), - repeat=True, - timetype=ba.TimeType.REAL) + self._update_timer = ba.Timer( + 1.0, + ba.WeakCall(self._update), + repeat=True, + timetype=ba.TimeType.REAL, + ) # Prevent taking any action until we've updated our state. self._waiting_for_initial_state = True @@ -153,6 +168,7 @@ class PrivateGatherTab(GatherTab): # pylint: disable=too-many-branches from bastd.ui.playlist import PlaylistTypeVars from ba.internal import filter_playlist + hcfg = PrivateHostingConfig() cfg = ba.app.config sessiontypestr = cfg.get('Private Party Host Session Type', 'ffa') @@ -170,23 +186,27 @@ class PrivateGatherTab(GatherTab): pvars = PlaylistTypeVars(sessiontype) playlist_name = ba.app.config.get( - f'{pvars.config_name} Playlist Selection') + f'{pvars.config_name} Playlist Selection' + ) if not isinstance(playlist_name, str): playlist_name = '__default__' - hcfg.playlist_name = (pvars.default_list_name.evaluate() - if playlist_name == '__default__' else - playlist_name) + hcfg.playlist_name = ( + pvars.default_list_name.evaluate() + if playlist_name == '__default__' + else playlist_name + ) playlist: list[dict[str, Any]] | None = None if playlist_name != '__default__': - playlist = (cfg.get(f'{pvars.config_name} Playlists', - {}).get(playlist_name)) + playlist = cfg.get(f'{pvars.config_name} Playlists', {}).get( + playlist_name + ) if playlist is None: playlist = pvars.get_default_list_call() - hcfg.playlist = filter_playlist(playlist, - sessiontype, - name=playlist_name) + hcfg.playlist = filter_playlist( + playlist, sessiontype, name=playlist_name + ) randomize = cfg.get(f'{pvars.config_name} Playlist Randomize') if not isinstance(randomize, bool): @@ -201,21 +221,27 @@ class PrivateGatherTab(GatherTab): if hcfg.session_type == 'teams': ctn: list[str] | None = cfg.get('Custom Team Names') if ctn is not None: - if (isinstance(ctn, (list, tuple)) and len(ctn) == 2 - and all(isinstance(x, str) for x in ctn)): + if ( + isinstance(ctn, (list, tuple)) + and len(ctn) == 2 + and all(isinstance(x, str) for x in ctn) + ): hcfg.custom_team_names = (ctn[0], ctn[1]) else: print(f'Found invalid custom-team-names data: {ctn}') ctc: list[list[float]] | None = cfg.get('Custom Team Colors') if ctc is not None: - if (isinstance(ctc, (list, tuple)) and len(ctc) == 2 - and all(isinstance(x, (list, tuple)) for x in ctc) - and all(len(x) == 3 for x in ctc)): - hcfg.custom_team_colors = ((ctc[0][0], ctc[0][1], - ctc[0][2]), - (ctc[1][0], ctc[1][1], - ctc[1][2])) + if ( + isinstance(ctc, (list, tuple)) + and len(ctc) == 2 + and all(isinstance(x, (list, tuple)) for x in ctc) + and all(len(x) == 3 for x in ctc) + ): + hcfg.custom_team_colors = ( + (ctc[0][0], ctc[0][1], ctc[0][2]), + (ctc[1][0], ctc[1][1], ctc[1][2]), + ) else: print(f'Found invalid custom-team-colors data: {ctc}') @@ -231,11 +257,15 @@ class PrivateGatherTab(GatherTab): except Exception: t_str = '?' if self._get_tickets_button: - ba.buttonwidget(edit=self._get_tickets_button, - label=ba.charstr(ba.SpecialChar.TICKET) + t_str) + ba.buttonwidget( + edit=self._get_tickets_button, + label=ba.charstr(ba.SpecialChar.TICKET) + t_str, + ) if self._ticket_count_text: - ba.textwidget(edit=self._ticket_count_text, - text=ba.charstr(ba.SpecialChar.TICKET) + t_str) + ba.textwidget( + edit=self._ticket_count_text, + text=ba.charstr(ba.SpecialChar.TICKET) + t_str, + ) def _update(self) -> None: """Periodic updating.""" @@ -247,14 +277,18 @@ class PrivateGatherTab(GatherTab): if self._state.sub_tab is SubTabType.HOST: # If we're not signed in, just refresh to show that. - if (ba.internal.get_v1_account_state() != 'signed_in' - and self._showing_not_signed_in_screen): + if ( + ba.internal.get_v1_account_state() != 'signed_in' + and self._showing_not_signed_in_screen + ): self._refresh_sub_tab() else: # Query an updated state periodically. - if (self._last_hosting_state_query_time is None - or now - self._last_hosting_state_query_time > 15.0): + if ( + self._last_hosting_state_query_time is None + or now - self._last_hosting_state_query_time > 15.0 + ): self._debug_server_comm('querying private party state') if ba.internal.get_v1_account_state() == 'signed_in': ba.internal.add_transaction( @@ -263,15 +297,17 @@ class PrivateGatherTab(GatherTab): 'expire_time': time.time() + 20, }, callback=ba.WeakCall( - self._hosting_state_idle_response), + self._hosting_state_idle_response + ), ) ba.internal.run_transactions() else: self._hosting_state_idle_response(None) self._last_hosting_state_query_time = now - def _hosting_state_idle_response(self, - result: dict[str, Any] | None) -> None: + def _hosting_state_idle_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 @@ -279,10 +315,13 @@ class PrivateGatherTab(GatherTab): # 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): - self._debug_server_comm('ignoring private party state response' - ' due to recent action') + if ( + self._last_action_send_time is not None + and time.time() - self._last_action_send_time < 5.0 + ): + self._debug_server_comm( + 'ignoring private party state response due to recent action' + ) return self._hosting_state_response(result) @@ -297,9 +336,9 @@ class PrivateGatherTab(GatherTab): if result is not None: self._debug_server_comm('got private party state response') try: - state = dataclass_from_dict(PrivateHostingState, - result, - discard_unknown_attrs=True) + state = dataclass_from_dict( + PrivateHostingState, result, discard_unknown_attrs=True + ) except Exception: ba.print_exception('Got invalid PrivateHostingState data') else: @@ -335,10 +374,12 @@ class PrivateGatherTab(GatherTab): inactive_color = (0.5, 0.4, 0.5) ba.textwidget( edit=self._join_sub_tab_text, - color=active_color if value is SubTabType.JOIN else inactive_color) + color=active_color if value is SubTabType.JOIN else inactive_color, + ) ba.textwidget( edit=self._host_sub_tab_text, - color=active_color if value is SubTabType.HOST else inactive_color) + color=active_color if value is SubTabType.HOST else inactive_color, + ) self._refresh_sub_tab() @@ -348,9 +389,11 @@ class PrivateGatherTab(GatherTab): def _selwidgets(self) -> list[ba.Widget | None]: """An indexed list of widgets we can use for saving/restoring sel.""" return [ - self._host_playlist_button, self._host_copy_button, - self._host_connect_button, self._host_start_stop_button, - self._get_tickets_button + self._host_playlist_button, + self._host_copy_button, + self._host_connect_button, + self._host_start_stop_button, + self._get_tickets_button, ] def _refresh_sub_tab(self) -> None: @@ -369,8 +412,8 @@ class PrivateGatherTab(GatherTab): # Clear anything existing in the old sub-tab. for widget in self._container.get_children(): if widget and widget not in { - self._host_sub_tab_text, - self._join_sub_tab_text, + self._host_sub_tab_text, + self._join_sub_tab_text, }: widget.delete() @@ -385,20 +428,23 @@ class PrivateGatherTab(GatherTab): if selindex is not None: selwidget = self._selwidgets()[selindex] if selwidget: - ba.containerwidget(edit=self._container, - selected_child=selwidget) + ba.containerwidget( + edit=self._container, selected_child=selwidget + ) def _build_join_tab(self) -> None: - ba.textwidget(parent=self._container, - position=(self._c_width * 0.5, self._c_height - 140), - color=(0.5, 0.46, 0.5), - scale=1.5, - size=(0, 0), - maxwidth=250, - h_align='center', - v_align='center', - text=ba.Lstr(resource='gatherWindow.partyCodeText')) + ba.textwidget( + parent=self._container, + position=(self._c_width * 0.5, self._c_height - 140), + color=(0.5, 0.46, 0.5), + scale=1.5, + size=(0, 0), + maxwidth=250, + h_align='center', + v_align='center', + text=ba.Lstr(resource='gatherWindow.partyCodeText'), + ) self._join_party_code_text = ba.textwidget( parent=self._container, @@ -412,17 +458,19 @@ class PrivateGatherTab(GatherTab): maxwidth=250, h_align='left', v_align='center', - text='') - btn = ba.buttonwidget(parent=self._container, - size=(300, 70), - label=ba.Lstr(resource='gatherWindow.' - 'manualConnectText'), - position=(self._c_width * 0.5 - 150, - self._c_height - 350), - on_activate_call=self._join_connect_press, - autoselect=True) - ba.textwidget(edit=self._join_party_code_text, - on_return_press_call=btn.activate) + text='', + ) + btn = ba.buttonwidget( + parent=self._container, + size=(300, 70), + label=ba.Lstr(resource='gatherWindow.' 'manualConnectText'), + position=(self._c_width * 0.5 - 150, self._c_height - 350), + on_activate_call=self._join_connect_press, + autoselect=True, + ) + ba.textwidget( + edit=self._join_party_code_text, on_return_press_call=btn.activate + ) def _on_get_tickets_press(self) -> None: @@ -431,23 +479,27 @@ class PrivateGatherTab(GatherTab): # Bring up get-tickets window and then kill ourself (we're on the # overlay layer so we'd show up above it). - getcurrency.GetCurrencyWindow(modal=True, - origin_widget=self._get_tickets_button) + getcurrency.GetCurrencyWindow( + modal=True, origin_widget=self._get_tickets_button + ) def _build_host_tab(self) -> None: # pylint: disable=too-many-branches # pylint: disable=too-many-statements + hostingstate = self._hostingstate if ba.internal.get_v1_account_state() != 'signed_in': - ba.textwidget(parent=self._container, - size=(0, 0), - h_align='center', - v_align='center', - maxwidth=200, - scale=0.8, - color=(0.6, 0.56, 0.6), - position=(self._c_width * 0.5, self._c_height * 0.5), - text=ba.Lstr(resource='notSignedInErrorText')) + ba.textwidget( + parent=self._container, + size=(0, 0), + h_align='center', + v_align='center', + maxwidth=200, + scale=0.8, + color=(0.6, 0.56, 0.6), + position=(self._c_width * 0.5, self._c_height * 0.5), + text=ba.Lstr(resource='notSignedInErrorText'), + ) self._showing_not_signed_in_screen = True return self._showing_not_signed_in_screen = False @@ -477,36 +529,44 @@ class PrivateGatherTab(GatherTab): # If we're not currently hosting and hosting requires tickets, # Show our count (possibly with a link to purchase more). - if (not self._waiting_for_initial_state - and self._hostingstate.party_code is None - and self._hostingstate.tickets_to_host_now != 0): + if ( + not self._waiting_for_initial_state + and hostingstate.party_code is None + and hostingstate.tickets_to_host_now != 0 + ): if not ba.app.ui.use_toolbars: if ba.app.allow_ticket_purchases: self._get_tickets_button = ba.buttonwidget( parent=self._container, - position=(self._c_width - 210 + 125, - self._c_height - 44), + position=( + self._c_width - 210 + 125, + self._c_height - 44, + ), autoselect=True, scale=0.6, size=(120, 60), textcolor=(0.2, 1, 0.2), label=ba.charstr(ba.SpecialChar.TICKET), color=(0.65, 0.5, 0.8), - on_activate_call=self._on_get_tickets_press) + on_activate_call=self._on_get_tickets_press, + ) else: self._ticket_count_text = ba.textwidget( parent=self._container, scale=0.6, - position=(self._c_width - 210 + 125, - self._c_height - 44), + position=( + self._c_width - 210 + 125, + self._c_height - 44, + ), color=(0.2, 1, 0.2), h_align='center', - v_align='center') + v_align='center', + ) # Set initial ticket count. self._update_currency_ui() v = self._c_height - 90 - if self._hostingstate.party_code is None: + if hostingstate.party_code is None: ba.textwidget( parent=self._container, size=(0, 0), @@ -518,20 +578,24 @@ class PrivateGatherTab(GatherTab): color=(0.5, 0.46, 0.5), position=(self._c_width * 0.5, v), text=ba.Lstr( - resource='gatherWindow.privatePartyCloudDescriptionText')) + resource='gatherWindow.privatePartyCloudDescriptionText' + ), + ) v -= 100 - if self._hostingstate.party_code is None: + if hostingstate.party_code is None: # We've got no current party running; show options to set one up. - ba.textwidget(parent=self._container, - size=(0, 0), - h_align='right', - v_align='center', - maxwidth=200, - scale=0.8, - color=(0.6, 0.56, 0.6), - position=(self._c_width * 0.5 - 210, v), - text=ba.Lstr(resource='playlistText')) + ba.textwidget( + parent=self._container, + size=(0, 0), + h_align='right', + v_align='center', + maxwidth=200, + scale=0.8, + color=(0.6, 0.56, 0.6), + position=(self._c_width * 0.5 - 210, v), + text=ba.Lstr(resource='playlistText'), + ) self._host_playlist_button = ba.buttonwidget( parent=self._container, size=(400, 70), @@ -541,13 +605,16 @@ class PrivateGatherTab(GatherTab): on_activate_call=self._playlist_press, position=(self._c_width * 0.5 - 200, v - 35), up_widget=self._host_sub_tab_text, - autoselect=True) + autoselect=True, + ) # If it appears we're coming back from playlist selection, # re-select our playlist button. if ba.app.ui.selecting_private_party_playlist: - ba.containerwidget(edit=self._container, - selected_child=self._host_playlist_button) + ba.containerwidget( + edit=self._container, + selected_child=self._host_playlist_button, + ) ba.app.ui.selecting_private_party_playlist = False else: # We've got a current party; show its info. @@ -560,24 +627,29 @@ class PrivateGatherTab(GatherTab): scale=0.9, color=(0.7, 0.64, 0.7), position=(self._c_width * 0.5, v + 90), - text=ba.Lstr(resource='gatherWindow.partyServerRunningText')) - ba.textwidget(parent=self._container, - size=(0, 0), - h_align='center', - v_align='center', - maxwidth=600, - scale=0.7, - color=(0.7, 0.64, 0.7), - position=(self._c_width * 0.5, v + 50), - text=ba.Lstr(resource='gatherWindow.partyCodeText')) - ba.textwidget(parent=self._container, - size=(0, 0), - h_align='center', - v_align='center', - scale=2.0, - color=(0.0, 1.0, 0.0), - position=(self._c_width * 0.5, v + 10), - text=self._hostingstate.party_code) + text=ba.Lstr(resource='gatherWindow.partyServerRunningText'), + ) + ba.textwidget( + parent=self._container, + size=(0, 0), + h_align='center', + v_align='center', + maxwidth=600, + scale=0.7, + color=(0.7, 0.64, 0.7), + position=(self._c_width * 0.5, v + 50), + text=ba.Lstr(resource='gatherWindow.partyCodeText'), + ) + ba.textwidget( + parent=self._container, + size=(0, 0), + h_align='center', + v_align='center', + scale=2.0, + color=(0.0, 1.0, 0.0), + position=(self._c_width * 0.5, v + 10), + text=hostingstate.party_code, + ) # Also action buttons to copy it and connect to it. if ba.clipboard_is_supported(): @@ -590,7 +662,8 @@ class PrivateGatherTab(GatherTab): label=ba.Lstr(resource='gatherWindow.copyCodeText'), on_activate_call=self._host_copy_press, position=(self._c_width * 0.5 - 150, v - 70), - autoselect=True) + autoselect=True, + ) else: cbtnoffs = -70 self._host_connect_button = ba.buttonwidget( @@ -601,7 +674,8 @@ class PrivateGatherTab(GatherTab): label=ba.Lstr(resource='gatherWindow.manualConnectText'), on_activate_call=self._host_connect_press, position=(self._c_width * 0.5 + cbtnoffs, v - 70), - autoselect=True) + autoselect=True, + ) v -= 120 @@ -610,7 +684,7 @@ class PrivateGatherTab(GatherTab): # If we don't want to show anything until we get a state: if self._waiting_for_initial_state: pass - elif self._hostingstate.unavailable_error is not None: + elif hostingstate.unavailable_error is not None: # If hosting is unavailable, show the associated reason. ba.textwidget( parent=self._container, @@ -622,9 +696,14 @@ class PrivateGatherTab(GatherTab): flatness=1.0, color=(1.0, 0.0, 0.0), position=(self._c_width * 0.5, v), - text=ba.Lstr(translate=('serverResponses', - self._hostingstate.unavailable_error))) - elif self._hostingstate.free_host_minutes_remaining is not None: + text=ba.Lstr( + translate=( + 'serverResponses', + hostingstate.unavailable_error, + ) + ), + ) + elif hostingstate.free_host_minutes_remaining is not None: # If we've been pre-approved to start/stop for free, show that. ba.textwidget( parent=self._container, @@ -634,20 +713,27 @@ class PrivateGatherTab(GatherTab): maxwidth=self._c_width * 0.9, scale=0.7, flatness=1.0, - color=((0.7, 0.64, 0.7) if self._hostingstate.party_code else - (0.0, 1.0, 0.0)), + color=( + (0.7, 0.64, 0.7) + if hostingstate.party_code + else (0.0, 1.0, 0.0) + ), position=(self._c_width * 0.5, v), text=ba.Lstr( resource='gatherWindow.startStopHostingMinutesText', - subs=[( - '${MINUTES}', - f'{self._hostingstate.free_host_minutes_remaining:.0f}' - )])) + subs=[ + ( + '${MINUTES}', + f'{hostingstate.free_host_minutes_remaining:.0f}', + ) + ], + ), + ) else: # Otherwise tell whether the free cloud server is available # or will be at some point. - if self._hostingstate.party_code is None: - if self._hostingstate.tickets_to_host_now == 0: + if hostingstate.party_code is None: + if hostingstate.tickets_to_host_now == 0: ba.textwidget( parent=self._container, size=(0, 0), @@ -659,10 +745,13 @@ class PrivateGatherTab(GatherTab): color=(0.0, 1.0, 0.0), position=(self._c_width * 0.5, v), text=ba.Lstr( - resource= - 'gatherWindow.freeCloudServerAvailableNowText')) + resource=( + 'gatherWindow.freeCloudServerAvailableNowText' + ) + ), + ) else: - if self._hostingstate.minutes_until_free_host is None: + if hostingstate.minutes_until_free_host is None: ba.textwidget( parent=self._container, size=(0, 0), @@ -674,11 +763,14 @@ class PrivateGatherTab(GatherTab): color=(1.0, 0.6, 0.0), position=(self._c_width * 0.5, v), text=ba.Lstr( - resource= - 'gatherWindow.freeCloudServerNotAvailableText') + resource=( + 'gatherWindow' + '.freeCloudServerNotAvailableText' + ) + ), ) else: - availmins = self._hostingstate.minutes_until_free_host + availmins = hostingstate.minutes_until_free_host ba.textwidget( parent=self._container, size=(0, 0), @@ -689,47 +781,60 @@ class PrivateGatherTab(GatherTab): flatness=1.0, color=(1.0, 0.6, 0.0), position=(self._c_width * 0.5, v), - text=ba.Lstr(resource='gatherWindow.' - 'freeCloudServerAvailableMinutesText', - subs=[('${MINUTES}', - f'{availmins:.0f}')])) + text=ba.Lstr( + resource='gatherWindow.' + 'freeCloudServerAvailableMinutesText', + subs=[('${MINUTES}', f'{availmins:.0f}')], + ), + ) v -= 100 - if (self._waiting_for_start_stop_response - or self._waiting_for_initial_state): + if ( + self._waiting_for_start_stop_response + or self._waiting_for_initial_state + ): btnlabel = ba.Lstr(resource='oneMomentText') else: - if self._hostingstate.unavailable_error is not None: + if hostingstate.unavailable_error is not None: btnlabel = ba.Lstr( - resource='gatherWindow.hostingUnavailableText') - elif self._hostingstate.party_code is None: + resource='gatherWindow.hostingUnavailableText' + ) + elif hostingstate.party_code is None: ticon = ba.internal.charstr(ba.SpecialChar.TICKET) - nowtickets = self._hostingstate.tickets_to_host_now + nowtickets = hostingstate.tickets_to_host_now if nowtickets > 0: btnlabel = ba.Lstr( resource='gatherWindow.startHostingPaidText', - subs=[('${COST}', f'{ticon}{nowtickets}')]) + subs=[('${COST}', f'{ticon}{nowtickets}')], + ) else: - btnlabel = ba.Lstr( - resource='gatherWindow.startHostingText') + btnlabel = ba.Lstr(resource='gatherWindow.startHostingText') else: btnlabel = ba.Lstr(resource='gatherWindow.stopHostingText') - disabled = (self._hostingstate.unavailable_error is not None - or self._waiting_for_initial_state) + disabled = ( + hostingstate.unavailable_error is not None + or self._waiting_for_initial_state + ) waiting = self._waiting_for_start_stop_response self._host_start_stop_button = ba.buttonwidget( parent=self._container, size=(400, 80), - color=((0.6, 0.6, 0.6) if disabled else - (0.5, 1.0, 0.5) if waiting else None), + color=( + (0.6, 0.6, 0.6) + if disabled + else (0.5, 1.0, 0.5) + if waiting + else None + ), enable_sound=False, label=btnlabel, textcolor=((0.7, 0.7, 0.7) if disabled else None), position=(self._c_width * 0.5 - 200, v), on_activate_call=self._start_stop_button_press, - autoselect=True) + autoselect=True, + ) def _playlist_press(self) -> None: assert self._host_playlist_button is not None @@ -746,18 +851,23 @@ class PrivateGatherTab(GatherTab): def _debug_server_comm(self, msg: str) -> None: if DEBUG_SERVER_COMMUNICATION: - print(f'PPTABCOM: {msg} at time ' - f'{time.time()-self._create_time:.2f}') + print( + f'PPTABCOM: {msg} at time ' + f'{time.time()-self._create_time:.2f}' + ) 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) now = time.time() - if (self._connect_press_time is not None - and now - self._connect_press_time < 5.0): + if ( + self._connect_press_time is not None + and now - self._connect_press_time < 5.0 + ): self._debug_server_comm( - 'not sending private party connect (too soon)') + 'not sending private party connect (too soon)' + ) return self._connect_press_time = now @@ -766,15 +876,17 @@ class PrivateGatherTab(GatherTab): { 'type': 'PRIVATE_PARTY_CONNECT', 'expire_time': time.time() + 20, - 'code': code + 'code': code, }, callback=ba.WeakCall(self._connect_response), ) ba.internal.run_transactions() def _start_stop_button_press(self) -> None: - if (self._waiting_for_start_stop_response - or self._waiting_for_initial_state): + if ( + self._waiting_for_start_stop_response + or self._waiting_for_initial_state + ): return if ba.internal.get_v1_account_state() != 'signed_in': @@ -813,7 +925,8 @@ class PrivateGatherTab(GatherTab): 'region_pings': ba.app.net.zone_pings, 'expire_time': time.time() + 20, }, - callback=ba.WeakCall(self._hosting_state_response)) + callback=ba.WeakCall(self._hosting_state_response), + ) ba.internal.run_transactions() else: @@ -823,7 +936,8 @@ class PrivateGatherTab(GatherTab): 'type': 'PRIVATE_PARTY_STOP', 'expire_time': time.time() + 20, }, - callback=ba.WeakCall(self._hosting_state_response)) + callback=ba.WeakCall(self._hosting_state_response), + ) ba.internal.run_transactions() ba.playsound(ba.getsound('click01')) @@ -839,7 +953,8 @@ class PrivateGatherTab(GatherTab): if not code: ba.screenmessage( ba.Lstr(resource='internal.invalidAddressErrorText'), - color=(1, 0, 0)) + color=(1, 0, 0), + ) ba.playsound(ba.getsound('error')) return @@ -850,14 +965,15 @@ class PrivateGatherTab(GatherTab): self._connect_press_time = None if result is None: raise RuntimeError() - cresult = dataclass_from_dict(PrivatePartyConnectResult, - result, - discard_unknown_attrs=True) + cresult = dataclass_from_dict( + PrivatePartyConnectResult, result, discard_unknown_attrs=True + ) if cresult.error is not None: self._debug_server_comm('got error connect response') ba.screenmessage( ba.Lstr(translate=('serverResponses', cresult.error)), - (1, 0, 0)) + (1, 0, 0), + ) ba.playsound(ba.getsound('error')) return self._debug_server_comm('got valid connect response') diff --git a/dist/ba_data/python/bastd/ui/gather/publictab.py b/dist/ba_data/python/bastd/ui/gather/publictab.py index 522c9e3..fafd19b 100644 --- a/dist/ba_data/python/bastd/ui/gather/publictab.py +++ b/dist/ba_data/python/bastd/ui/gather/publictab.py @@ -27,6 +27,7 @@ DEBUG_PROCESSING = False class SubTabType(Enum): """Available sub-tabs.""" + JOIN = 'join' HOST = 'host' @@ -34,6 +35,7 @@ class SubTabType(Enum): @dataclass class PartyEntry: """Info about a public party.""" + address: str index: int queue: str | None = None @@ -69,17 +71,27 @@ class UIRow: def _clear(self) -> None: for widget in [ - self._name_widget, self._size_widget, self._ping_widget, - self._stats_button + self._name_widget, + self._size_widget, + self._ping_widget, + self._stats_button, ]: if widget: widget.delete() - def update(self, index: int, party: PartyEntry, sub_scroll_width: float, - sub_scroll_height: float, lineheight: float, - columnwidget: ba.Widget, join_text: ba.Widget, - filter_text: ba.Widget, existing_selection: Selection | None, - tab: PublicGatherTab) -> None: + def update( + self, + index: int, + party: PartyEntry, + sub_scroll_width: float, + sub_scroll_height: float, + lineheight: float, + columnwidget: ba.Widget, + join_text: ba.Widget, + filter_text: ba.Widget, + existing_selection: Selection | None, + tab: PublicGatherTab, + ) -> None: """Update for the given data.""" # pylint: disable=too-many-locals @@ -102,7 +114,8 @@ class UIRow: selectable=True, on_select_call=ba.WeakCall( tab.set_public_party_selection, - Selection(party.get_key(), SelectionComponent.NAME)), + Selection(party.get_key(), SelectionComponent.NAME), + ), on_activate_call=ba.WeakCall(tab.on_public_party_activate, party), click_activate=True, maxwidth=sub_scroll_width * 0.45, @@ -110,20 +123,27 @@ class UIRow: autoselect=True, color=(1, 1, 1, 0.3 if party.ping is None else 1.0), h_align='left', - v_align='center') - ba.widget(edit=self._name_widget, - left_widget=join_text, - show_buffer_top=64.0, - show_buffer_bottom=64.0) - if existing_selection == Selection(party.get_key(), - SelectionComponent.NAME): - ba.containerwidget(edit=columnwidget, - selected_child=self._name_widget) + v_align='center', + ) + ba.widget( + edit=self._name_widget, + left_widget=join_text, + show_buffer_top=64.0, + show_buffer_bottom=64.0, + ) + if existing_selection == Selection( + party.get_key(), SelectionComponent.NAME + ): + ba.containerwidget( + edit=columnwidget, selected_child=self._name_widget + ) if party.stats_addr: url = party.stats_addr.replace( '${ACCOUNT}', ba.internal.get_v1_account_misc_read_val_2( - 'resolvedAccountID', 'UNKNOWN')) + 'resolvedAccountID', 'UNKNOWN' + ), + ) self._stats_button = ba.buttonwidget( color=(0.3, 0.6, 0.94), textcolor=(1.0, 1.0, 1.0), @@ -133,15 +153,18 @@ class UIRow: on_activate_call=ba.Call(ba.open_url, url), on_select_call=ba.WeakCall( tab.set_public_party_selection, - Selection(party.get_key(), - SelectionComponent.STATS_BUTTON)), + Selection(party.get_key(), SelectionComponent.STATS_BUTTON), + ), size=(120, 40), position=(sub_scroll_width * 0.66 + hpos, 1 + vpos), - scale=0.9) + scale=0.9, + ) if existing_selection == Selection( - party.get_key(), SelectionComponent.STATS_BUTTON): - ba.containerwidget(edit=columnwidget, - selected_child=self._stats_button) + party.get_key(), SelectionComponent.STATS_BUTTON + ): + ba.containerwidget( + edit=columnwidget, selected_child=self._stats_button + ) self._size_widget = ba.textwidget( text=str(party.size) + '/' + str(party.size_max), @@ -151,29 +174,36 @@ class UIRow: scale=0.7, color=(0.8, 0.8, 0.8), h_align='right', - v_align='center') + v_align='center', + ) if index == 0: ba.widget(edit=self._name_widget, up_widget=filter_text) if self._stats_button: ba.widget(edit=self._stats_button, up_widget=filter_text) - self._ping_widget = ba.textwidget(parent=columnwidget, - size=(0, 0), - position=(sub_scroll_width * 0.94 + - hpos, 20 + vpos), - scale=0.7, - h_align='right', - v_align='center') + self._ping_widget = ba.textwidget( + parent=columnwidget, + size=(0, 0), + position=(sub_scroll_width * 0.94 + hpos, 20 + vpos), + scale=0.7, + h_align='right', + v_align='center', + ) if party.ping is None: - ba.textwidget(edit=self._ping_widget, - text='-', - color=(0.5, 0.5, 0.5)) + ba.textwidget( + edit=self._ping_widget, text='-', color=(0.5, 0.5, 0.5) + ) else: - ba.textwidget(edit=self._ping_widget, - text=str(int(party.ping)), - color=(0, 1, 0) if party.ping <= ping_good else - (1, 1, 0) if party.ping <= ping_med else (1, 0, 0)) + ba.textwidget( + edit=self._ping_widget, + text=str(int(party.ping)), + color=(0, 1, 0) + if party.ping <= ping_good + else (1, 1, 0) + if party.ping <= ping_med + else (1, 0, 0), + ) party.clean_display_index = index @@ -181,6 +211,7 @@ class UIRow: @dataclass class State: """State saved/restored only while the app is running.""" + sub_tab: SubTabType = SubTabType.JOIN parties: list[tuple[str, PartyEntry]] | None = None next_entry_index: int = 0 @@ -191,6 +222,7 @@ class State: class SelectionComponent(Enum): """Describes what part of an entry is selected.""" + NAME = 'name' STATS_BUTTON = 'stats_button' @@ -198,6 +230,7 @@ class SelectionComponent(Enum): @dataclass class Selection: """Describes the currently selected list element.""" + entry_key: str component: SelectionComponent @@ -213,6 +246,7 @@ class AddrFetchThread(threading.Thread): try: # FIXME: Update this to work with IPv6 at some point. import socket + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.connect(('8.8.8.8', 80)) val = sock.getsockname()[0] @@ -220,6 +254,7 @@ class AddrFetchThread(threading.Thread): ba.pushcall(ba.Call(self._call, val), from_other_thread=True) except Exception as exc: from efro.error import is_udp_communication_error + # Ignore expected network errors; log others. if is_udp_communication_error(exc): pass @@ -230,8 +265,12 @@ class AddrFetchThread(threading.Thread): class PingThread(threading.Thread): """Thread for sending out game pings.""" - def __init__(self, address: str, port: int, - call: Callable[[str, int, float | None], int | None]): + def __init__( + self, + address: str, + port: int, + call: Callable[[str, int, float | None], int | None], + ): super().__init__() self._address = address self._port = port @@ -243,6 +282,7 @@ class PingThread(threading.Thread): try: import socket from ba.internal import get_ip_address_type + socket_type = get_ip_address_type(self._address) sock = socket.socket(socket_type, socket.SOCK_DGRAM) sock.connect((self._address, self._port)) @@ -267,11 +307,18 @@ class PingThread(threading.Thread): break time.sleep(1) ping = (time.time() - starttime) * 1000.0 - ba.pushcall(ba.Call(self._call, self._address, self._port, - ping if accessible else None), - from_other_thread=True) + ba.pushcall( + ba.Call( + self._call, + self._address, + self._port, + ping if accessible else None, + ), + from_other_thread=True, + ) except Exception as exc: from efro.error import is_udp_communication_error + if is_udp_communication_error(exc): pass else: @@ -347,11 +394,14 @@ class PublicGatherTab(GatherTab): c_height = region_height - 20 self._container = ba.containerwidget( parent=parent_widget, - position=(region_left, - region_bottom + (region_height - c_height) * 0.5), + position=( + region_left, + region_bottom + (region_height - c_height) * 0.5, + ), size=(c_width, c_height), background=False, - selection_loops_to_parent=True) + selection_loops_to_parent=True, + ) v = c_height - 30 self._join_text = ba.textwidget( parent=self._container, @@ -371,8 +421,10 @@ class PublicGatherTab(GatherTab): region_height, playsound=True, ), - text=ba.Lstr(resource='gatherWindow.' - 'joinPublicPartyDescriptionText')) + text=ba.Lstr( + resource='gatherWindow.' 'joinPublicPartyDescriptionText' + ), + ) self._host_text = ba.textwidget( parent=self._container, position=(c_width * 0.5 + 45, v - 13), @@ -391,12 +443,16 @@ class PublicGatherTab(GatherTab): region_height, playsound=True, ), - text=ba.Lstr(resource='gatherWindow.' - 'hostPublicPartyDescriptionText')) + text=ba.Lstr( + resource='gatherWindow.' 'hostPublicPartyDescriptionText' + ), + ) ba.widget(edit=self._join_text, up_widget=tab_button) - ba.widget(edit=self._host_text, - left_widget=self._join_text, - up_widget=tab_button) + ba.widget( + edit=self._host_text, + left_widget=self._join_text, + up_widget=tab_button, + ) ba.widget(edit=self._join_text, right_widget=self._host_text) # Attempt to fetch our local address so we have it for error messages. @@ -404,10 +460,12 @@ class PublicGatherTab(GatherTab): AddrFetchThread(ba.WeakCall(self._fetch_local_addr_cb)).start() self._set_sub_tab(self._sub_tab, region_width, region_height) - self._update_timer = ba.Timer(0.1, - ba.WeakCall(self._update), - repeat=True, - timetype=ba.TimeType.REAL) + self._update_timer = ba.Timer( + 0.1, + ba.WeakCall(self._update), + repeat=True, + timetype=ba.TimeType.REAL, + ) return self._container def on_deactivate(self) -> None: @@ -425,7 +483,8 @@ class PublicGatherTab(GatherTab): next_entry_index=self._next_entry_index, filter_value=self._filter_value, have_server_list_response=self._have_server_list_response, - have_valid_server_list=self._have_valid_server_list) + have_valid_server_list=self._have_valid_server_list, + ) def restore_state(self) -> None: state = ba.app.ui.window_states.get(type(self)) @@ -437,8 +496,7 @@ class PublicGatherTab(GatherTab): # Restore the parties we stored. if state.parties: self._parties = { - key: copy.copy(party) - for key, party in state.parties + key: copy.copy(party) for key, party in state.parties } self._parties_sorted = list(self._parties.items()) self._party_lists_dirty = True @@ -450,11 +508,13 @@ class PublicGatherTab(GatherTab): self._have_valid_server_list = state.have_valid_server_list self._filter_value = state.filter_value - def _set_sub_tab(self, - value: SubTabType, - region_width: float, - region_height: float, - playsound: bool = False) -> None: + def _set_sub_tab( + self, + value: SubTabType, + region_width: float, + region_height: float, + playsound: bool = False, + ) -> None: assert self._container if playsound: ba.playsound(ba.getsound('click01')) @@ -475,10 +535,12 @@ class PublicGatherTab(GatherTab): inactive_color = (0.5, 0.4, 0.5) ba.textwidget( edit=self._join_text, - color=active_color if value is SubTabType.JOIN else inactive_color) + color=active_color if value is SubTabType.JOIN else inactive_color, + ) ba.textwidget( edit=self._host_text, - color=active_color if value is SubTabType.HOST else inactive_color) + color=active_color if value is SubTabType.HOST else inactive_color, + ) # Clear anything existing in the old sub-tab. for widget in self._container.get_children(): @@ -491,8 +553,9 @@ class PublicGatherTab(GatherTab): if value is SubTabType.HOST: self._build_host_tab(region_width, region_height) - def _build_join_tab(self, region_width: float, - region_height: float) -> None: + def _build_join_tab( + self, region_width: float, region_height: float + ) -> None: c_width = region_width c_height = region_height - 20 sub_scroll_height = c_height - 125 @@ -500,56 +563,66 @@ class PublicGatherTab(GatherTab): v = c_height - 35 v -= 60 filter_txt = ba.Lstr(resource='filterText') - self._filter_text = ba.textwidget(parent=self._container, - text=self._filter_value, - size=(350, 45), - position=(290, v - 10), - h_align='left', - v_align='center', - editable=True, - description=filter_txt) + self._filter_text = ba.textwidget( + parent=self._container, + text=self._filter_value, + size=(350, 45), + position=(290, v - 10), + h_align='left', + v_align='center', + editable=True, + description=filter_txt, + ) ba.widget(edit=self._filter_text, up_widget=self._join_text) - ba.textwidget(text=filter_txt, - parent=self._container, - size=(0, 0), - position=(270, v + 13), - maxwidth=150, - scale=0.8, - color=(0.5, 0.46, 0.5), - flatness=1.0, - h_align='right', - v_align='center') + ba.textwidget( + text=filter_txt, + parent=self._container, + size=(0, 0), + position=(270, v + 13), + maxwidth=150, + scale=0.8, + color=(0.5, 0.46, 0.5), + flatness=1.0, + h_align='right', + v_align='center', + ) - ba.textwidget(text=ba.Lstr(resource='nameText'), - parent=self._container, - size=(0, 0), - position=(90, v - 8), - maxwidth=60, - scale=0.6, - color=(0.5, 0.46, 0.5), - flatness=1.0, - h_align='center', - v_align='center') - ba.textwidget(text=ba.Lstr(resource='gatherWindow.partySizeText'), - parent=self._container, - size=(0, 0), - position=(755, v - 8), - maxwidth=60, - scale=0.6, - color=(0.5, 0.46, 0.5), - flatness=1.0, - h_align='center', - v_align='center') - ba.textwidget(text=ba.Lstr(resource='gatherWindow.pingText'), - parent=self._container, - size=(0, 0), - position=(825, v - 8), - maxwidth=60, - scale=0.6, - color=(0.5, 0.46, 0.5), - flatness=1.0, - h_align='center', - v_align='center') + ba.textwidget( + text=ba.Lstr(resource='nameText'), + parent=self._container, + size=(0, 0), + position=(90, v - 8), + maxwidth=60, + scale=0.6, + color=(0.5, 0.46, 0.5), + flatness=1.0, + h_align='center', + v_align='center', + ) + ba.textwidget( + text=ba.Lstr(resource='gatherWindow.partySizeText'), + parent=self._container, + size=(0, 0), + position=(755, v - 8), + maxwidth=60, + scale=0.6, + color=(0.5, 0.46, 0.5), + flatness=1.0, + h_align='center', + v_align='center', + ) + ba.textwidget( + text=ba.Lstr(resource='gatherWindow.pingText'), + parent=self._container, + size=(0, 0), + position=(825, v - 8), + maxwidth=60, + scale=0.6, + color=(0.5, 0.46, 0.5), + flatness=1.0, + h_align='center', + v_align='center', + ) v -= sub_scroll_height + 23 self._host_scrollwidget = scrollw = ba.scrollwidget( parent=self._container, @@ -558,26 +631,31 @@ class PublicGatherTab(GatherTab): size=(sub_scroll_width, sub_scroll_height), claims_up_down=False, claims_left_right=True, - autoselect=True) - self._join_list_column = ba.containerwidget(parent=scrollw, - background=False, - size=(400, 400), - claims_left_right=True) - self._join_status_text = ba.textwidget(parent=self._container, - text='', - size=(0, 0), - scale=0.9, - flatness=1.0, - shadow=0.0, - h_align='center', - v_align='top', - maxwidth=c_width, - color=(0.6, 0.6, 0.6), - position=(c_width * 0.5, - c_height * 0.5)) + autoselect=True, + ) + self._join_list_column = ba.containerwidget( + parent=scrollw, + background=False, + size=(400, 400), + claims_left_right=True, + ) + self._join_status_text = ba.textwidget( + parent=self._container, + text='', + size=(0, 0), + scale=0.9, + flatness=1.0, + shadow=0.0, + h_align='center', + v_align='top', + maxwidth=c_width, + color=(0.6, 0.6, 0.6), + position=(c_width * 0.5, c_height * 0.5), + ) - def _build_host_tab(self, region_width: float, - region_height: float) -> None: + def _build_host_tab( + self, region_width: float, region_height: float + ) -> None: c_width = region_width c_height = region_height - 20 v = c_height - 35 @@ -595,46 +673,55 @@ class PublicGatherTab(GatherTab): flatness=1.0, color=(0.5, 0.46, 0.5), position=(region_width * 0.5, v + 10), - text=ba.Lstr(resource='gatherWindow.publicHostRouterConfigText')) + text=ba.Lstr(resource='gatherWindow.publicHostRouterConfigText'), + ) v -= 30 party_name_text = ba.Lstr( resource='gatherWindow.partyNameText', - fallback_resource='editGameListWindow.nameText') - ba.textwidget(parent=self._container, - size=(0, 0), - h_align='right', - v_align='center', - maxwidth=200, - scale=0.8, - color=ba.app.ui.infotextcolor, - position=(210, v - 9), - text=party_name_text) - self._host_name_text = ba.textwidget(parent=self._container, - editable=True, - size=(535, 40), - position=(230, v - 30), - text=ba.app.config.get( - 'Public Party Name', ''), - maxwidth=494, - shadow=0.3, - flatness=1.0, - description=party_name_text, - autoselect=True, - v_align='center', - corner_scale=1.0) + fallback_resource='editGameListWindow.nameText', + ) + ba.textwidget( + parent=self._container, + size=(0, 0), + h_align='right', + v_align='center', + maxwidth=200, + scale=0.8, + color=ba.app.ui.infotextcolor, + position=(210, v - 9), + text=party_name_text, + ) + self._host_name_text = ba.textwidget( + parent=self._container, + editable=True, + size=(535, 40), + position=(230, v - 30), + text=ba.app.config.get('Public Party Name', ''), + maxwidth=494, + shadow=0.3, + flatness=1.0, + description=party_name_text, + autoselect=True, + v_align='center', + corner_scale=1.0, + ) v -= 60 - ba.textwidget(parent=self._container, - size=(0, 0), - h_align='right', - v_align='center', - maxwidth=200, - scale=0.8, - color=ba.app.ui.infotextcolor, - position=(210, v - 9), - text=ba.Lstr(resource='maxPartySizeText', - fallback_resource='maxConnectionsText')) + ba.textwidget( + parent=self._container, + size=(0, 0), + h_align='right', + v_align='center', + maxwidth=200, + scale=0.8, + color=ba.app.ui.infotextcolor, + position=(210, v - 9), + text=ba.Lstr( + resource='maxPartySizeText', + fallback_resource='maxConnectionsText', + ), + ) self._host_max_party_size_value = ba.textwidget( parent=self._container, size=(0, 0), @@ -643,42 +730,51 @@ class PublicGatherTab(GatherTab): scale=1.2, color=(1, 1, 1), position=(240, v - 9), - text=str(ba.internal.get_public_party_max_size())) - btn1 = self._host_max_party_size_minus_button = (ba.buttonwidget( + text=str(ba.internal.get_public_party_max_size()), + ) + btn1 = self._host_max_party_size_minus_button = ba.buttonwidget( parent=self._container, size=(40, 40), on_activate_call=ba.WeakCall( - self._on_max_public_party_size_minus_press), + self._on_max_public_party_size_minus_press + ), position=(280, v - 26), label='-', - autoselect=True)) - btn2 = self._host_max_party_size_plus_button = (ba.buttonwidget( + autoselect=True, + ) + btn2 = self._host_max_party_size_plus_button = ba.buttonwidget( parent=self._container, size=(40, 40), on_activate_call=ba.WeakCall( - self._on_max_public_party_size_plus_press), + self._on_max_public_party_size_plus_press + ), position=(350, v - 26), label='+', - autoselect=True)) + autoselect=True, + ) v -= 50 v -= 70 if is_public_enabled: label = ba.Lstr( resource='gatherWindow.makePartyPrivateText', - fallback_resource='gatherWindow.stopAdvertisingText') + fallback_resource='gatherWindow.stopAdvertisingText', + ) else: label = ba.Lstr( resource='gatherWindow.makePartyPublicText', - fallback_resource='gatherWindow.startAdvertisingText') + fallback_resource='gatherWindow.startAdvertisingText', + ) self._host_toggle_button = ba.buttonwidget( parent=self._container, label=label, size=(400, 80), on_activate_call=self._on_stop_advertising_press - if is_public_enabled else self._on_start_advertizing_press, + if is_public_enabled + else self._on_start_advertizing_press, position=(c_width * 0.5 - 200, v), autoselect=True, - up_widget=btn2) + up_widget=btn2, + ) ba.widget(edit=self._host_name_text, down_widget=btn2) ba.widget(edit=btn2, up_widget=self._host_name_text) ba.widget(edit=btn1, up_widget=self._host_name_text) @@ -686,8 +782,7 @@ class PublicGatherTab(GatherTab): v -= 10 self._host_status_text = ba.textwidget( parent=self._container, - text=ba.Lstr(resource='gatherWindow.' - 'partyStatusNotPublicText'), + text=ba.Lstr(resource='gatherWindow.' 'partyStatusNotPublicText'), size=(0, 0), scale=0.7, flatness=1.0, @@ -695,7 +790,8 @@ class PublicGatherTab(GatherTab): v_align='top', maxwidth=c_width * 0.9, color=(0.6, 0.56, 0.6), - position=(c_width * 0.5, v)) + position=(c_width * 0.5, v), + ) v -= 90 ba.textwidget( parent=self._container, @@ -707,15 +803,17 @@ class PublicGatherTab(GatherTab): v_align='center', maxwidth=c_width * 0.9, color=(0.5, 0.46, 0.5), - position=(c_width * 0.5, v)) + position=(c_width * 0.5, v), + ) # If public sharing is already on, # launch a status-check immediately. if ba.internal.get_public_party_enabled(): self._do_status_check() - def _on_public_party_query_result(self, - result: dict[str, Any] | None) -> None: + def _on_public_party_query_result( + self, result: dict[str, Any] | None + ) -> None: starttime = time.time() self._have_server_list_response = True @@ -748,18 +846,17 @@ class PublicGatherTab(GatherTab): if party is not None: party.claimed = True self._parties = { - key: val - for key, val in list(self._parties.items()) if val.claimed + key: val for key, val in list(self._parties.items()) if val.claimed } - self._parties_sorted = [ - p for p in self._parties_sorted if p[1].claimed - ] + self._parties_sorted = [p for p in self._parties_sorted if p[1].claimed] self._party_lists_dirty = True # self._update_server_list() if DEBUG_PROCESSING: - print(f'Handled public party query results in ' - f'{time.time()-starttime:.5f}s.') + print( + f'Handled public party query results in ' + f'{time.time()-starttime:.5f}s.' + ) def _update(self) -> None: """Periodic updating.""" @@ -812,8 +909,9 @@ class PublicGatherTab(GatherTab): status_text = self._join_status_text if status_text: if not signed_in: - ba.textwidget(edit=status_text, - text=ba.Lstr(resource='notSignedInText')) + ba.textwidget( + edit=status_text, text=ba.Lstr(resource='notSignedInText') + ) else: # If we have a valid list, show no status; just the list. # Otherwise show either 'loading...' or 'error' depending @@ -822,16 +920,22 @@ class PublicGatherTab(GatherTab): ba.textwidget(edit=status_text, text='') else: if self._have_server_list_response: - ba.textwidget(edit=status_text, - text=ba.Lstr(resource='errorText')) + ba.textwidget( + edit=status_text, text=ba.Lstr(resource='errorText') + ) else: ba.textwidget( edit=status_text, text=ba.Lstr( value='${A}...', - subs=[('${A}', - ba.Lstr(resource='store.loadingText'))], - )) + subs=[ + ( + '${A}', + ba.Lstr(resource='store.loadingText'), + ) + ], + ), + ) self._update_party_rows() @@ -845,8 +949,10 @@ class PublicGatherTab(GatherTab): # Janky - allow escaping when there's nothing in our list. assert self._host_scrollwidget - ba.containerwidget(edit=self._host_scrollwidget, - claims_up_down=(len(self._parties_displayed) > 0)) + ba.containerwidget( + edit=self._host_scrollwidget, + claims_up_down=(len(self._parties_displayed) > 0), + ) # Clip if we have more UI rows than parties to show. clipcount = len(self._ui_rows) - len(self._parties_displayed) @@ -861,8 +967,9 @@ class PublicGatherTab(GatherTab): sub_scroll_width = 830 lineheight = 42 sub_scroll_height = lineheight * len(self._parties_displayed) + 50 - ba.containerwidget(edit=columnwidget, - size=(sub_scroll_width, sub_scroll_height)) + ba.containerwidget( + edit=columnwidget, size=(sub_scroll_width, sub_scroll_height) + ) # Any time our height changes, reset the refresh back to the top # so we don't see ugly empty spaces appearing during initial list @@ -913,7 +1020,8 @@ class PublicGatherTab(GatherTab): join_text=self._join_text, existing_selection=self._selection, filter_text=self._filter_text, - tab=self) + tab=self, + ) self._refresh_ui_row = refresh_row + 1 rowcount -= 1 @@ -939,10 +1047,12 @@ class PublicGatherTab(GatherTab): party = self._parties.get(party_key) if party is None: # If this party is new to us, init it. - party = PartyEntry(address=addr, - next_ping_time=ba.time(ba.TimeType.REAL) + - 0.001 * party_in['pd'], - index=self._next_entry_index) + party = PartyEntry( + address=addr, + next_ping_time=ba.time(ba.TimeType.REAL) + + 0.001 * party_in['pd'], + index=self._next_entry_index, + ) self._parties[party_key] = party self._parties_sorted.append((party_key, party)) self._party_lists_dirty = True @@ -971,8 +1081,10 @@ class PublicGatherTab(GatherTab): party.clean_display_index = None if DEBUG_PROCESSING and parties_in: - print(f'Processed {len(parties_in)} raw party infos in' - f' {time.time()-starttime:.5f}s.') + print( + f'Processed {len(parties_in)} raw party infos in' + f' {time.time()-starttime:.5f}s.' + ) def _update_party_lists(self) -> None: if not self._party_lists_dirty: @@ -980,14 +1092,19 @@ class PublicGatherTab(GatherTab): starttime = time.time() assert len(self._parties_sorted) == len(self._parties) - self._parties_sorted.sort(key=lambda p: ( - p[1].queue is None, # Show non-queued last. - p[1].ping if p[1].ping is not None else 999999.0, - p[1].index)) + self._parties_sorted.sort( + key=lambda p: ( + p[1].queue is None, # Show non-queued last. + p[1].ping if p[1].ping is not None else 999999.0, + p[1].index, + ) + ) # If signed out or errored, show no parties. - if (ba.internal.get_v1_account_state() != 'signed_in' - or not self._have_valid_server_list): + if ( + ba.internal.get_v1_account_state() != 'signed_in' + or not self._have_valid_server_list + ): self._parties_displayed = {} else: if self._filter_value: @@ -1002,8 +1119,10 @@ class PublicGatherTab(GatherTab): # Any time our selection disappears from the displayed list, go back to # auto-selecting the top entry. - if (self._selection is not None - and self._selection.entry_key not in self._parties_displayed): + if ( + self._selection is not None + and self._selection.entry_key not in self._parties_displayed + ): self._have_user_selected_row = False # Whenever the user hasn't selected something, keep the first visible @@ -1014,17 +1133,23 @@ class PublicGatherTab(GatherTab): self._party_lists_dirty = False if DEBUG_PROCESSING: - print(f'Sorted {len(self._parties_sorted)} parties in' - f' {time.time()-starttime:.5f}s.') + print( + f'Sorted {len(self._parties_sorted)} parties in' + f' {time.time()-starttime:.5f}s.' + ) def _query_party_list_periodically(self) -> None: now = ba.time(ba.TimeType.REAL) # Fire off a new public-party query periodically. - if (self._last_server_list_query_time is None - or now - self._last_server_list_query_time > - 0.001 * ba.internal.get_v1_account_misc_read_val( - 'pubPartyRefreshMS', 10000)): + if ( + self._last_server_list_query_time is None + or now - self._last_server_list_query_time + > 0.001 + * ba.internal.get_v1_account_misc_read_val( + 'pubPartyRefreshMS', 10000 + ) + ): self._last_server_list_query_time = now if DEBUG_SERVER_COMMUNICATION: print('REQUESTING SERVER LIST') @@ -1033,9 +1158,10 @@ class PublicGatherTab(GatherTab): { 'type': 'PUBLIC_PARTY_QUERY', 'proto': ba.app.protocol_version, - 'lang': ba.app.lang.language + 'lang': ba.app.lang.language, }, - callback=ba.WeakCall(self._on_public_party_query_result)) + callback=ba.WeakCall(self._on_public_party_query_result), + ) ba.internal.run_transactions() else: self._on_public_party_query_result(None) @@ -1057,23 +1183,28 @@ class PublicGatherTab(GatherTab): elif party.ping_attempts > 2: mult = 5 if party.ping is not None: - mult = (10 if party.ping > 300 else - 5 if party.ping > 150 else 2) + mult = ( + 10 if party.ping > 300 else 5 if party.ping > 150 else 2 + ) interval = party.ping_interval * mult if DEBUG_SERVER_COMMUNICATION: - print(f'pinging #{party.index} cur={party.ping} ' - f'interval={interval} ' - f'({party.ping_responses}/{party.ping_attempts})') + print( + f'pinging #{party.index} cur={party.ping} ' + f'interval={interval} ' + f'({party.ping_responses}/{party.ping_attempts})' + ) party.next_ping_time = now + party.ping_interval * mult party.ping_attempts += 1 - PingThread(party.address, party.port, - ba.WeakCall(self._ping_callback)).start() + PingThread( + party.address, party.port, ba.WeakCall(self._ping_callback) + ).start() - def _ping_callback(self, address: str, port: int | None, - result: float | None) -> None: + def _ping_callback( + self, address: str, port: int | None, result: float | None + ) -> None: # Look for a widget corresponding to this target. # If we find one, update our list. party_key = f'{address}_{port}' @@ -1085,11 +1216,11 @@ class PublicGatherTab(GatherTab): # We now smooth ping a bit to reduce jumping around in the list # (only where pings are relatively good). current_ping = party.ping - if (current_ping is not None and result is not None - and result < 150): + if current_ping is not None and result is not None and result < 150: smoothing = 0.7 - party.ping = (smoothing * current_ping + - (1.0 - smoothing) * result) + party.ping = ( + smoothing * current_ping + (1.0 - smoothing) * result + ) else: party.ping = result @@ -1101,7 +1232,8 @@ class PublicGatherTab(GatherTab): self._local_address = str(val) def _on_public_party_accessible_response( - self, data: dict[str, Any] | None) -> None: + self, data: dict[str, Any] | None + ) -> None: # If we've got status text widgets, update them. text = self._host_status_text @@ -1109,8 +1241,9 @@ class PublicGatherTab(GatherTab): if data is None: ba.textwidget( edit=text, - text=ba.Lstr(resource='gatherWindow.' - 'partyStatusNoConnectionText'), + text=ba.Lstr( + resource='gatherWindow.' 'partyStatusNoConnectionText' + ), color=(1, 0, 0), ) else: @@ -1119,10 +1252,17 @@ class PublicGatherTab(GatherTab): if self._local_address is not None: ex_line = ba.Lstr( value='\n${A} ${B}', - subs=[('${A}', - ba.Lstr(resource='gatherWindow.' - 'manualYourLocalAddressText')), - ('${B}', self._local_address)]) + subs=[ + ( + '${A}', + ba.Lstr( + resource='gatherWindow.' + 'manualYourLocalAddressText' + ), + ), + ('${B}', self._local_address), + ], + ) else: ex_line = '' ba.textwidget( @@ -1130,44 +1270,69 @@ class PublicGatherTab(GatherTab): text=ba.Lstr( value='${A}\n${B}${C}', subs=[ - ('${A}', - ba.Lstr(resource='gatherWindow.' - 'partyStatusNotJoinableText')), - ('${B}', - ba.Lstr(resource='gatherWindow.' - 'manualRouterForwardingText', - subs=[ - ('${PORT}', - str(ba.internal.get_game_port())) - ])), ('${C}', ex_line) - ]), - color=(1, 0, 0)) + ( + '${A}', + ba.Lstr( + resource='gatherWindow.' + 'partyStatusNotJoinableText' + ), + ), + ( + '${B}', + ba.Lstr( + resource='gatherWindow.' + 'manualRouterForwardingText', + subs=[ + ( + '${PORT}', + str( + ba.internal.get_game_port() + ), + ) + ], + ), + ), + ('${C}', ex_line), + ], + ), + color=(1, 0, 0), + ) else: - ba.textwidget(edit=text, - text=ba.Lstr(resource='gatherWindow.' - 'partyStatusJoinableText'), - color=(0, 1, 0)) + ba.textwidget( + edit=text, + text=ba.Lstr( + resource='gatherWindow.' 'partyStatusJoinableText' + ), + color=(0, 1, 0), + ) def _do_status_check(self) -> None: from ba.internal import master_server_get - ba.textwidget(edit=self._host_status_text, - color=(1, 1, 0), - text=ba.Lstr(resource='gatherWindow.' - 'partyStatusCheckingText')) - master_server_get('bsAccessCheck', {'b': ba.app.build_number}, - callback=ba.WeakCall( - self._on_public_party_accessible_response)) + + ba.textwidget( + edit=self._host_status_text, + color=(1, 1, 0), + text=ba.Lstr(resource='gatherWindow.' 'partyStatusCheckingText'), + ) + master_server_get( + 'bsAccessCheck', + {'b': ba.app.build_number}, + callback=ba.WeakCall(self._on_public_party_accessible_response), + ) def _on_start_advertizing_press(self) -> None: from bastd.ui.account import show_sign_in_prompt + if ba.internal.get_v1_account_state() != 'signed_in': show_sign_in_prompt() return name = cast(str, ba.textwidget(query=self._host_name_text)) if name == '': - ba.screenmessage(ba.Lstr(resource='internal.invalidNameErrorText'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr(resource='internal.invalidNameErrorText'), + color=(1, 0, 0), + ) ba.playsound(ba.getsound('error')) return ba.internal.set_public_party_name(name) @@ -1186,8 +1351,10 @@ class PublicGatherTab(GatherTab): edit=self._host_toggle_button, label=ba.Lstr( resource='gatherWindow.makePartyPrivateText', - fallback_resource='gatherWindow.stopAdvertisingText'), - on_activate_call=self._on_stop_advertising_press) + fallback_resource='gatherWindow.stopAdvertisingText', + ), + on_activate_call=self._on_stop_advertising_press, + ) def _on_stop_advertising_press(self) -> None: ba.internal.set_public_party_enabled(False) @@ -1200,22 +1367,26 @@ class PublicGatherTab(GatherTab): if text: ba.textwidget( edit=text, - text=ba.Lstr(resource='gatherWindow.' - 'partyStatusNotPublicText'), + text=ba.Lstr( + resource='gatherWindow.' 'partyStatusNotPublicText' + ), color=(0.6, 0.6, 0.6), ) ba.buttonwidget( edit=self._host_toggle_button, label=ba.Lstr( resource='gatherWindow.makePartyPublicText', - fallback_resource='gatherWindow.startAdvertisingText'), - on_activate_call=self._on_start_advertizing_press) + fallback_resource='gatherWindow.startAdvertisingText', + ), + on_activate_call=self._on_start_advertizing_press, + ) def on_public_party_activate(self, party: PartyEntry) -> None: """Called when a party is clicked or otherwise activated.""" self.save_state() if party.queue is not None: from bastd.ui.partyqueue import PartyQueueWindow + ba.playsound(ba.getsound('swish')) PartyQueueWindow(party.queue, party.address, party.port) else: diff --git a/dist/ba_data/python/bastd/ui/getcurrency.py b/dist/ba_data/python/bastd/ui/getcurrency.py index 549fec4..6d4cd90 100644 --- a/dist/ba_data/python/bastd/ui/getcurrency.py +++ b/dist/ba_data/python/bastd/ui/getcurrency.py @@ -16,12 +16,14 @@ if TYPE_CHECKING: class GetCurrencyWindow(ba.Window): """Window for purchasing/acquiring currency.""" - def __init__(self, - transition: str = 'in_right', - from_modal_store: bool = False, - modal: bool = False, - origin_widget: ba.Widget | None = None, - store_back_location: str | None = None): + def __init__( + self, + transition: str = 'in_right', + from_modal_store: bool = False, + modal: bool = False, + origin_widget: ba.Widget | None = None, + store_back_location: str | None = None, + ): # pylint: disable=too-many-statements # pylint: disable=too-many-locals @@ -58,14 +60,22 @@ class GetCurrencyWindow(ba.Window): top_extra = 20 if uiscale is ba.UIScale.SMALL else 0 - super().__init__(root_widget=ba.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 ba.UIScale.SMALL else - 1.2 if uiscale is ba.UIScale.MEDIUM else 1.0), - stack_offset=(0, -3) if uiscale is ba.UIScale.SMALL else (0, 0))) + super().__init__( + root_widget=ba.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 ba.UIScale.SMALL + else 1.2 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + stack_offset=(0, -3) if uiscale is ba.UIScale.SMALL else (0, 0), + ) + ) btn = ba.buttonwidget( parent=self._root_widget, @@ -75,25 +85,30 @@ class GetCurrencyWindow(ba.Window): autoselect=True, label=ba.Lstr(resource='doneText' if modal else 'backText'), button_type='regular' if modal else 'back', - on_activate_call=self._back) + on_activate_call=self._back, + ) ba.containerwidget(edit=self._root_widget, cancel_button=btn) - ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, self._height - 55), - size=(0, 0), - color=ba.app.ui.title_color, - scale=1.2, - h_align='center', - v_align='center', - text=ba.Lstr(resource=self._r + '.titleText'), - maxwidth=290) + ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, self._height - 55), + size=(0, 0), + color=ba.app.ui.title_color, + scale=1.2, + h_align='center', + v_align='center', + text=ba.Lstr(resource=self._r + '.titleText'), + maxwidth=290, + ) if not modal: - ba.buttonwidget(edit=btn, - button_type='backSmall', - size=(60, 60), - label=ba.charstr(ba.SpecialChar.BACK)) + ba.buttonwidget( + edit=btn, + button_type='backSmall', + size=(60, 60), + label=ba.charstr(ba.SpecialChar.BACK), + ) b_size = (220.0, 180.0) v = self._height - b_size[1] - 80 @@ -101,16 +116,18 @@ class GetCurrencyWindow(ba.Window): self._ad_button = None - def _add_button(item: str, - position: tuple[float, float], - size: tuple[float, float], - label: ba.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) -> ba.Widget: + def _add_button( + item: str, + position: tuple[float, float], + size: tuple[float, float], + label: ba.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, + ) -> ba.Widget: btn2 = ba.buttonwidget( parent=self._root_widget, position=position, @@ -119,42 +136,57 @@ class GetCurrencyWindow(ba.Window): label='', autoselect=True, color=None if enabled else (0.5, 0.5, 0.5), - on_activate_call=(ba.Call(self._purchase, item) - if enabled else self._disabled_press)) - txt = ba.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)) + on_activate_call=( + ba.Call(self._purchase, item) + if enabled + else self._disabled_press + ), + ) + txt = ba.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: - ba.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)) + ba.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 = ba.imagewidget( parent=self._root_widget, texture=ba.gettexture(tex_name), - position=(position[0] + size[0] * 0.5 - tex_size * 0.5, - position[1] + size[1] * 0.66 - tex_size * 0.5), + 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)) + opacity=tex_opacity * (1.0 if enabled else 0.25), + ) if item == 'ad': self._ad_button = btn2 self._ad_label = txt @@ -163,39 +195,74 @@ class GetCurrencyWindow(ba.Window): self._ad_time_text = ba.textwidget( parent=self._root_widget, text='1m 10s', - position=(position[0] + size[0] * 0.5, - position[1] + size[1] * 0.5), + 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)) + color=(0.4, 0.9, 0.4, 1.0), + ) return btn2 rsrc = self._r + '.ticketsText' - c2txt = ba.Lstr(resource=rsrc, - subs=[('${COUNT}', - str( - ba.internal.get_v1_account_misc_read_val( - 'tickets2Amount', 500)))]) - c3txt = ba.Lstr(resource=rsrc, - subs=[('${COUNT}', - str( - ba.internal.get_v1_account_misc_read_val( - 'tickets3Amount', 1500)))]) - c4txt = ba.Lstr(resource=rsrc, - subs=[('${COUNT}', - str( - ba.internal.get_v1_account_misc_read_val( - 'tickets4Amount', 5000)))]) - c5txt = ba.Lstr(resource=rsrc, - subs=[('${COUNT}', - str( - ba.internal.get_v1_account_misc_read_val( - 'tickets5Amount', 15000)))]) + c2txt = ba.Lstr( + resource=rsrc, + subs=[ + ( + '${COUNT}', + str( + ba.internal.get_v1_account_misc_read_val( + 'tickets2Amount', 500 + ) + ), + ) + ], + ) + c3txt = ba.Lstr( + resource=rsrc, + subs=[ + ( + '${COUNT}', + str( + ba.internal.get_v1_account_misc_read_val( + 'tickets3Amount', 1500 + ) + ), + ) + ], + ) + c4txt = ba.Lstr( + resource=rsrc, + subs=[ + ( + '${COUNT}', + str( + ba.internal.get_v1_account_misc_read_val( + 'tickets4Amount', 5000 + ) + ), + ) + ], + ) + c5txt = ba.Lstr( + resource=rsrc, + subs=[ + ( + '${COUNT}', + str( + ba.internal.get_v1_account_misc_read_val( + 'tickets5Amount', 15000 + ) + ), + ) + ], + ) h = 110.0 @@ -212,41 +279,57 @@ class GetCurrencyWindow(ba.Window): # 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 + _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 + _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 = ba.internal.has_video_ads() h = self._width * 0.5 + 110.0 @@ -261,35 +344,49 @@ class GetCurrencyWindow(ba.Window): size=b_size_3, label=ba.Lstr( resource=self._r + '.ticketsFromASponsorText', - subs=[('${COUNT}', - str( - ba.internal.get_v1_account_misc_read_val( - 'sponsorTickets', 5)))]), + subs=[ + ( + '${COUNT}', + str( + ba.internal.get_v1_account_misc_read_val( + 'sponsorTickets', 5 + ) + ), + ) + ], + ), tex_name='ticketsMore', enabled=self._enable_ad_button, tex_opacity=0.6, tex_scale=0.7, - text_scale=0.7) - ba.buttonwidget(edit=cdb, - color=(0.65, 0.5, - 0.7) if self._enable_ad_button else - (0.5, 0.5, 0.5)) + text_scale=0.7, + ) + ba.buttonwidget( + edit=cdb, + color=(0.65, 0.5, 0.7) + if self._enable_ad_button + else (0.5, 0.5, 0.5), + ) self._ad_free_text = ba.textwidget( parent=self._root_widget, text=ba.Lstr(resource=self._r + '.freeText'), - position=(h + h_offs + b_size_3[0] * 0.5, - v + b_size_3[1] * 0.5 + 25), + 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) if self._enable_ad_button else - (1, 1, 1, 0.2), + color=(1, 1, 0, 1.0) + if self._enable_ad_button + else (1, 1, 1, 0.2), draw_controller=cdb, rotate=15, shadow=1.0, maxwidth=150, h_align='center', v_align='center', - scale=1.0) + scale=1.0, + ) v -= 125 else: v -= 20 @@ -303,80 +400,106 @@ class GetCurrencyWindow(ba.Window): size=b_size_3, label=ba.Lstr( resource='gatherWindow.earnTicketsForRecommendingText', - subs=[('${COUNT}', - str( - ba.internal.get_v1_account_misc_read_val( - 'sponsorTickets', 5)))]), + subs=[ + ( + '${COUNT}', + str( + ba.internal.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) + text_scale=0.7, + ) ba.buttonwidget(edit=cdb, color=(0.65, 0.5, 0.7)) - ba.textwidget(parent=self._root_widget, - text=ba.Lstr(resource=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) + ba.textwidget( + parent=self._root_widget, + text=ba.Lstr(resource=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 h = self._width - (185 + x_inset) v = self._height - 95 + tc_y_offs - txt1 = (ba.Lstr( - resource=self._r + - '.youHaveText').evaluate().partition('${COUNT}')[0].strip()) - txt2 = (ba.Lstr( - resource=self._r + - '.youHaveText').evaluate().rpartition('${COUNT}')[-1].strip()) + txt1 = ( + ba.Lstr(resource=self._r + '.youHaveText') + .evaluate() + .partition('${COUNT}')[0] + .strip() + ) + txt2 = ( + ba.Lstr(resource=self._r + '.youHaveText') + .evaluate() + .rpartition('${COUNT}')[-1] + .strip() + ) - ba.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) + ba.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 = ba.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) + self._ticket_count_text = ba.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 - ba.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) + ba.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, + ) # update count now and once per second going forward.. self._ticking_node: ba.Node | None = None self._smooth_ticket_count: float | None = None self._ticket_count = 0 self._update() - self._update_timer = ba.Timer(1.0, - ba.WeakCall(self._update), - timetype=ba.TimeType.REAL, - repeat=True) + self._update_timer = ba.Timer( + 1.0, + ba.WeakCall(self._update), + timetype=ba.TimeType.REAL, + repeat=True, + ) self._smooth_increase_speed = 1.0 def __del__(self) -> None: @@ -400,7 +523,8 @@ class GetCurrencyWindow(ba.Window): # 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) + self._ticket_count, + ) if int(self._smooth_ticket_count) >= self._ticket_count: finished = True self._smooth_ticket_count = float(self._ticket_count) @@ -410,11 +534,14 @@ class GetCurrencyWindow(ba.Window): 'sound', attrs={ 'sound': ba.getsound('scoreIncrease'), - 'positional': False - }) + 'positional': False, + }, + ) - ba.textwidget(edit=self._ticket_count_text, - text=str(int(self._smooth_ticket_count))) + ba.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: @@ -438,13 +565,16 @@ class GetCurrencyWindow(ba.Window): # available if self._ad_button is not None: next_reward_ad_time = ba.internal.get_v1_account_misc_read_val_2( - 'nextRewardAdTime', None) + 'nextRewardAdTime', None + ) if next_reward_ad_time is not None: next_reward_ad_time = datetime.datetime.utcfromtimestamp( - next_reward_ad_time) + next_reward_ad_time + ) now = datetime.datetime.utcnow() - if (ba.internal.have_incentivized_ad() and - (next_reward_ad_time is None or next_reward_ad_time <= now)): + if ba.internal.have_incentivized_ad() and ( + next_reward_ad_time is None or next_reward_ad_time <= now + ): self._ad_button_greyed = False ba.buttonwidget(edit=self._ad_button, color=(0.65, 0.5, 0.7)) ba.textwidget(edit=self._ad_label, color=(0.7, 0.9, 0.7, 1.0)) @@ -458,12 +588,15 @@ class GetCurrencyWindow(ba.Window): ba.textwidget(edit=self._ad_free_text, color=(1, 1, 0, 0.2)) ba.imagewidget(edit=self._ad_image, opacity=0.6 * 0.25) sval: str | ba.Lstr - if (next_reward_ad_time is not None - and next_reward_ad_time > now): + if ( + next_reward_ad_time is not None + and next_reward_ad_time > now + ): sval = ba.timestring( (next_reward_ad_time - now).total_seconds() * 1000.0, centi=False, - timeformat=ba.TimeFormat.MILLISECONDS) + timeformat=ba.TimeFormat.MILLISECONDS, + ) else: sval = '' ba.textwidget(edit=self._ad_time_text, text=sval) @@ -474,42 +607,57 @@ class GetCurrencyWindow(ba.Window): 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 = ba.Timer(0.05, - ba.WeakCall( - self._smooth_update), - repeat=True, - timetype=ba.TimeType.REAL) + elif ( + self._ticket_count != int(self._smooth_ticket_count) + and self._smooth_update_timer is None + ): + self._smooth_update_timer = ba.Timer( + 0.05, + ba.WeakCall(self._smooth_update), + repeat=True, + timetype=ba.TimeType.REAL, + ) 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) + 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: # if we're on a platform without purchases, inform the user they # can link their accounts and buy stuff elsewhere app = ba.app - if ((app.test_build or - (app.platform == 'android' - and app.subplatform in ['oculus', 'cardboard'])) - and ba.internal.get_v1_account_misc_read_val( - 'allowAccountLinking2', False)): - ba.screenmessage(ba.Lstr(resource=self._r + - '.unavailableLinkAccountText'), - color=(1, 0.5, 0)) + if ( + app.test_build + or ( + app.platform == 'android' + and app.subplatform in ['oculus', 'cardboard'] + ) + ) and ba.internal.get_v1_account_misc_read_val( + 'allowAccountLinking2', False + ): + ba.screenmessage( + ba.Lstr(resource=self._r + '.unavailableLinkAccountText'), + color=(1, 0.5, 0), + ) else: - ba.screenmessage(ba.Lstr(resource=self._r + '.unavailableText'), - color=(1, 0.5, 0)) + ba.screenmessage( + ba.Lstr(resource=self._r + '.unavailableText'), + color=(1, 0.5, 0), + ) ba.playsound(ba.getsound('error')) def _purchase(self, item: str) -> None: from bastd.ui import account from bastd.ui import appinvite from ba.internal import master_server_get + if item == 'app_invite': if ba.internal.get_v1_account_state() != 'signed_in': account.show_sign_in_prompt() @@ -519,23 +667,27 @@ class GetCurrencyWindow(ba.Window): # 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 = ba.app - master_server_get('bsAccountPurchaseCheck', { - 'item': item, - 'platform': app.platform, - 'subplatform': app.subplatform, - 'version': app.version, - 'buildNumber': app.build_number - }, - callback=ba.WeakCall(self._purchase_check_result, - item)) + master_server_get( + 'bsAccountPurchaseCheck', + { + 'item': item, + 'platform': app.platform, + 'subplatform': app.subplatform, + 'version': app.version, + 'buildNumber': app.build_number, + }, + callback=ba.WeakCall(self._purchase_check_result, item), + ) - def _purchase_check_result(self, item: str, - result: dict[str, Any] | None) -> None: + def _purchase_check_result( + self, item: str, result: dict[str, Any] | None + ) -> None: if result is None: ba.playsound(ba.getsound('error')) ba.screenmessage( ba.Lstr(resource='internal.unavailableNoConnectionText'), - color=(1, 0, 0)) + color=(1, 0, 0), + ) else: if result['allow']: self._do_purchase(item) @@ -544,30 +696,39 @@ class GetCurrencyWindow(ba.Window): ba.playsound(ba.getsound('error')) ba.screenmessage( ba.Lstr(resource='getTicketsWindow.versionTooOldText'), - color=(1, 0, 0)) + color=(1, 0, 0), + ) else: ba.playsound(ba.getsound('error')) ba.screenmessage( ba.Lstr(resource='getTicketsWindow.unavailableText'), - color=(1, 0, 0)) + color=(1, 0, 0), + ) # actually start the purchase locally.. def _do_purchase(self, item: str) -> None: if item == 'ad': import datetime + # if ads are disabled until some time, error.. next_reward_ad_time = ba.internal.get_v1_account_misc_read_val_2( - 'nextRewardAdTime', None) + 'nextRewardAdTime', None + ) if next_reward_ad_time is not None: next_reward_ad_time = datetime.datetime.utcfromtimestamp( - next_reward_ad_time) + next_reward_ad_time + ) now = datetime.datetime.utcnow() - if ((next_reward_ad_time is not None and next_reward_ad_time > now) - or self._ad_button_greyed): + if ( + next_reward_ad_time is not None and next_reward_ad_time > now + ) or self._ad_button_greyed: ba.playsound(ba.getsound('error')) - ba.screenmessage(ba.Lstr( - resource='getTicketsWindow.unavailableTemporarilyText'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr( + resource='getTicketsWindow.unavailableTemporarilyText' + ), + color=(1, 0, 0), + ) elif self._enable_ad_button: ba.app.ads.show_ad('tickets') else: @@ -575,15 +736,18 @@ class GetCurrencyWindow(ba.Window): def _back(self) -> None: from bastd.ui.store import browser + if self._transitioning_out: return - ba.containerwidget(edit=self._root_widget, - transition=self._transition_out) + ba.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() + back_location=self._store_back_location, + ).get_root_widget() if not self._from_modal_store: ba.app.ui.set_main_menu_window(window) self._transitioning_out = True @@ -596,18 +760,29 @@ def show_get_tickets_prompt() -> None: depending on the build of the game. """ from bastd.ui.confirm import ConfirmWindow + if ba.app.allow_ticket_purchases: ConfirmWindow( - ba.Lstr(translate=('serverResponses', - 'You don\'t have enough tickets for this!')), + ba.Lstr( + translate=( + 'serverResponses', + 'You don\'t have enough tickets for this!', + ) + ), lambda: GetCurrencyWindow(modal=True), ok_text=ba.Lstr(resource='getTicketsWindow.titleText'), width=460, - height=130) + height=130, + ) else: ConfirmWindow( - ba.Lstr(translate=('serverResponses', - 'You don\'t have enough tickets for this!')), + ba.Lstr( + translate=( + 'serverResponses', + 'You don\'t have enough tickets for this!', + ) + ), cancel_button=False, width=460, - height=130) + height=130, + ) diff --git a/dist/ba_data/python/bastd/ui/getremote.py b/dist/ba_data/python/bastd/ui/getremote.py index 4f8f401..c25d5ff 100644 --- a/dist/ba_data/python/bastd/ui/getremote.py +++ b/dist/ba_data/python/bastd/ui/getremote.py @@ -19,17 +19,24 @@ class GetBSRemoteWindow(popup.PopupWindow): def __init__(self) -> None: position = (0.0, 0.0) uiscale = ba.app.ui.uiscale - scale = (2.3 if uiscale is ba.UIScale.SMALL else - 1.65 if uiscale is ba.UIScale.MEDIUM else 1.23) + scale = ( + 2.3 + if uiscale is ba.UIScale.SMALL + else 1.65 + if uiscale is ba.UIScale.MEDIUM + else 1.23 + ) self._transitioning_out = False self._width = 570 self._height = 350 bg_color = (0.5, 0.4, 0.6) - popup.PopupWindow.__init__(self, - position=position, - size=(self._width, self._height), - scale=scale, - bg_color=bg_color) + popup.PopupWindow.__init__( + self, + position=position, + size=(self._width, self._height), + scale=scale, + bg_color=bg_color, + ) self._cancel_button = ba.buttonwidget( parent=self.root_widget, position=(50, self._height - 30), @@ -40,23 +47,32 @@ class GetBSRemoteWindow(popup.PopupWindow): on_activate_call=self._on_cancel_press, autoselect=True, icon=ba.gettexture('crossOut'), - iconscale=1.2) - ba.imagewidget(parent=self.root_widget, - position=(self._width * 0.5 - 110, - self._height * 0.67 - 110), - size=(220, 220), - texture=ba.gettexture('multiplayerExamples')) - ba.textwidget(parent=self.root_widget, - size=(0, 0), - h_align='center', - v_align='center', - maxwidth=self._width * 0.9, - position=(self._width * 0.5, 60), - text=ba.Lstr( - resource='remoteAppInfoShortText', - subs=[('${APP_NAME}', ba.Lstr(resource='titleText')), - ('${REMOTE_APP_NAME}', - ba.Lstr(resource='remote_app.app_name'))])) + iconscale=1.2, + ) + ba.imagewidget( + parent=self.root_widget, + position=(self._width * 0.5 - 110, self._height * 0.67 - 110), + size=(220, 220), + texture=ba.gettexture('multiplayerExamples'), + ) + ba.textwidget( + parent=self.root_widget, + size=(0, 0), + h_align='center', + v_align='center', + maxwidth=self._width * 0.9, + position=(self._width * 0.5, 60), + text=ba.Lstr( + resource='remoteAppInfoShortText', + subs=[ + ('${APP_NAME}', ba.Lstr(resource='titleText')), + ( + '${REMOTE_APP_NAME}', + ba.Lstr(resource='remote_app.app_name'), + ), + ], + ), + ) def _on_cancel_press(self) -> None: self._transition_out() diff --git a/dist/ba_data/python/bastd/ui/helpui.py b/dist/ba_data/python/bastd/ui/helpui.py index 8983640..17baee0 100644 --- a/dist/ba_data/python/bastd/ui/helpui.py +++ b/dist/ba_data/python/bastd/ui/helpui.py @@ -16,12 +16,13 @@ if TYPE_CHECKING: class HelpWindow(ba.Window): """A window providing help on how to play.""" - def __init__(self, - main_menu: bool = False, - origin_widget: ba.Widget | None = None): + def __init__( + self, main_menu: bool = False, origin_widget: ba.Widget | None = None + ): # pylint: disable=too-many-statements # pylint: disable=too-many-locals from ba.internal import get_remote_app_name + ba.set_analytics_screen('Help Window') # If they provided an origin-widget, scale up from that. @@ -43,87 +44,121 @@ class HelpWindow(ba.Window): uiscale = ba.app.ui.uiscale width = 950 if uiscale is ba.UIScale.SMALL else 750 x_offs = 100 if uiscale is ba.UIScale.SMALL else 0 - height = (460 if uiscale is ba.UIScale.SMALL else - 530 if uiscale is ba.UIScale.MEDIUM else 600) + height = ( + 460 + if uiscale is ba.UIScale.SMALL + else 530 + if uiscale is ba.UIScale.MEDIUM + else 600 + ) - super().__init__(root_widget=ba.containerwidget( - size=(width, height), - transition=transition, - toolbar_visibility='menu_minimal', - scale_origin_stack_offset=scale_origin, - scale=(1.77 if uiscale is ba.UIScale.SMALL else - 1.25 if uiscale is ba.UIScale.MEDIUM else 1.0), - stack_offset=(0, -30) if uiscale is ba.UIScale.SMALL else ( - 0, 15) if uiscale is ba.UIScale.MEDIUM else (0, 0))) + super().__init__( + root_widget=ba.containerwidget( + size=(width, height), + transition=transition, + toolbar_visibility='menu_minimal', + scale_origin_stack_offset=scale_origin, + scale=( + 1.77 + if uiscale is ba.UIScale.SMALL + else 1.25 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + stack_offset=(0, -30) + if uiscale is ba.UIScale.SMALL + else (0, 15) + if uiscale is ba.UIScale.MEDIUM + else (0, 0), + ) + ) - ba.textwidget(parent=self._root_widget, - position=(0, height - - (50 if uiscale is ba.UIScale.SMALL else 45)), - size=(width, 25), - text=ba.Lstr(resource=self._r + '.titleText', - subs=[('${APP_NAME}', - ba.Lstr(resource='titleText'))]), - color=ba.app.ui.title_color, - h_align='center', - v_align='top') + ba.textwidget( + parent=self._root_widget, + position=(0, height - (50 if uiscale is ba.UIScale.SMALL else 45)), + size=(width, 25), + text=ba.Lstr( + resource=self._r + '.titleText', + subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))], + ), + color=ba.app.ui.title_color, + h_align='center', + v_align='top', + ) self._scrollwidget = ba.scrollwidget( parent=self._root_widget, position=(44 + x_offs, 55 if uiscale is ba.UIScale.SMALL else 55), simple_culling_v=100.0, - size=(width - (88 + 2 * x_offs), - height - 120 + (5 if uiscale is ba.UIScale.SMALL else 0)), - capture_arrows=True) + size=( + width - (88 + 2 * x_offs), + height - 120 + (5 if uiscale is ba.UIScale.SMALL else 0), + ), + capture_arrows=True, + ) if ba.app.ui.use_toolbars: ba.widget( edit=self._scrollwidget, - right_widget=ba.internal.get_special_widget('party_button')) - ba.containerwidget(edit=self._root_widget, - selected_child=self._scrollwidget) + right_widget=ba.internal.get_special_widget('party_button'), + ) + ba.containerwidget( + edit=self._root_widget, selected_child=self._scrollwidget + ) # ugly: create this last so it gets first dibs at touch events (since # we have it close to the scroll widget) if uiscale is ba.UIScale.SMALL and ba.app.ui.use_toolbars: - ba.containerwidget(edit=self._root_widget, - on_cancel_call=self._close) + ba.containerwidget( + edit=self._root_widget, on_cancel_call=self._close + ) ba.widget( edit=self._scrollwidget, - left_widget=ba.internal.get_special_widget('back_button')) + left_widget=ba.internal.get_special_widget('back_button'), + ) else: btn = ba.buttonwidget( parent=self._root_widget, - position=(x_offs + - (40 + 0 if uiscale is ba.UIScale.SMALL else 70), - height - - (59 if uiscale is ba.UIScale.SMALL else 50)), + position=( + x_offs + (40 + 0 if uiscale is ba.UIScale.SMALL else 70), + height - (59 if uiscale is ba.UIScale.SMALL else 50), + ), size=(140, 60), scale=0.7 if uiscale is ba.UIScale.SMALL else 0.8, - label=ba.Lstr( - resource='backText') if self._main_menu else 'Close', + label=ba.Lstr(resource='backText') + if self._main_menu + else 'Close', button_type='back' if self._main_menu else None, extra_touch_border_scale=2.0, autoselect=True, - on_activate_call=self._close) + on_activate_call=self._close, + ) ba.containerwidget(edit=self._root_widget, cancel_button=btn) if self._main_menu: - ba.buttonwidget(edit=btn, - button_type='backSmall', - size=(60, 55), - label=ba.charstr(ba.SpecialChar.BACK)) + ba.buttonwidget( + edit=btn, + button_type='backSmall', + size=(60, 55), + label=ba.charstr(ba.SpecialChar.BACK), + ) self._sub_width = 660 - self._sub_height = 1590 + ba.app.lang.get_resource( - self._r + '.someDaysExtraSpace') + ba.app.lang.get_resource( - self._r + '.orPunchingSomethingExtraSpace') + self._sub_height = ( + 1590 + + ba.app.lang.get_resource(self._r + '.someDaysExtraSpace') + + ba.app.lang.get_resource( + self._r + '.orPunchingSomethingExtraSpace' + ) + ) - self._subcontainer = ba.containerwidget(parent=self._scrollwidget, - size=(self._sub_width, - self._sub_height), - background=False, - claims_left_right=False, - claims_tab=False) + self._subcontainer = ba.containerwidget( + parent=self._scrollwidget, + size=(self._sub_width, self._sub_height), + background=False, + claims_left_right=False, + claims_tab=False, + ) spacing = 1.0 h = self._sub_width * 0.5 @@ -134,39 +169,45 @@ class HelpWindow(ba.Window): header2 = (0.8, 0.8, 1.0, 1.0) paragraph = (0.8, 0.8, 1.0, 1.0) - txt = ba.Lstr(resource=self._r + '.welcomeText', - subs=[('${APP_NAME}', ba.Lstr(resource='titleText')) - ]).evaluate() + txt = ba.Lstr( + resource=self._r + '.welcomeText', + subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))], + ).evaluate() txt_scale = 1.4 txt_maxwidth = 480 - ba.textwidget(parent=self._subcontainer, - position=(h, v), - size=(0, 0), - scale=txt_scale, - flatness=0.5, - res_scale=1.5, - text=txt, - h_align='center', - color=header, - v_align='center', - maxwidth=txt_maxwidth) + ba.textwidget( + parent=self._subcontainer, + position=(h, v), + size=(0, 0), + scale=txt_scale, + flatness=0.5, + res_scale=1.5, + text=txt, + h_align='center', + color=header, + v_align='center', + maxwidth=txt_maxwidth, + ) txt_width = min( txt_maxwidth, - ba.internal.get_string_width(txt, suppress_warning=True) * - txt_scale) + ba.internal.get_string_width(txt, suppress_warning=True) + * txt_scale, + ) icon_size = 70 hval2 = h - (txt_width * 0.5 + icon_size * 0.5 * icon_buffer) - ba.imagewidget(parent=self._subcontainer, - size=(icon_size, icon_size), - position=(hval2 - 0.5 * icon_size, - v - 0.45 * icon_size), - texture=logo_tex) + ba.imagewidget( + parent=self._subcontainer, + size=(icon_size, icon_size), + position=(hval2 - 0.5 * icon_size, v - 0.45 * icon_size), + texture=logo_tex, + ) force_test = False app = ba.app - if (app.platform == 'android' - and app.subplatform == 'alibaba') or force_test: + if ( + app.platform == 'android' and app.subplatform == 'alibaba' + ) or force_test: v -= 120.0 txtv = ( '\xe8\xbf\x99\xe6\x98\xaf\xe4\xb8\x80\xe4\xb8\xaa\xe5\x8f\xaf' @@ -185,201 +226,242 @@ class HelpWindow(ba.Window): '\xbf\xe5\xa4\x96\xe8\xae\xbe\xe3\x80\x82\n' '\xe6\x9c\x80\xe5\xa4\x9a\xe6\x94\xaf\xe6\x8c\x81\xe6\x8e\xa5' '\xe5\x85\xa5\xc2\xa08\xc2\xa0\xe4\xb8\xaa\xe5\xa4\x96\xe8' - '\xae\xbe') - ba.textwidget(parent=self._subcontainer, - size=(0, 0), - h_align='center', - v_align='center', - maxwidth=self._sub_width * 0.9, - position=(self._sub_width * 0.5, v - 180), - text=txtv) - ba.imagewidget(parent=self._subcontainer, - position=(self._sub_width - 320, v - 120), - size=(200, 200), - texture=ba.gettexture('aliControllerQR')) - ba.imagewidget(parent=self._subcontainer, - position=(90, v - 130), - size=(210, 210), - texture=ba.gettexture('multiplayerExamples')) + '\xae\xbe' + ) + ba.textwidget( + parent=self._subcontainer, + size=(0, 0), + h_align='center', + v_align='center', + maxwidth=self._sub_width * 0.9, + position=(self._sub_width * 0.5, v - 180), + text=txtv, + ) + ba.imagewidget( + parent=self._subcontainer, + position=(self._sub_width - 320, v - 120), + size=(200, 200), + texture=ba.gettexture('aliControllerQR'), + ) + ba.imagewidget( + parent=self._subcontainer, + position=(90, v - 130), + size=(210, 210), + texture=ba.gettexture('multiplayerExamples'), + ) v -= 120.0 else: v -= spacing * 50.0 txt = ba.Lstr(resource=self._r + '.someDaysText').evaluate() - ba.textwidget(parent=self._subcontainer, - position=(h, v), - size=(0, 0), - scale=1.2, - maxwidth=self._sub_width * 0.9, - text=txt, - h_align='center', - color=paragraph, - v_align='center', - flatness=1.0) - v -= (spacing * 25.0 + getres(self._r + '.someDaysExtraSpace')) + ba.textwidget( + parent=self._subcontainer, + position=(h, v), + size=(0, 0), + scale=1.2, + maxwidth=self._sub_width * 0.9, + text=txt, + h_align='center', + color=paragraph, + v_align='center', + flatness=1.0, + ) + v -= spacing * 25.0 + getres(self._r + '.someDaysExtraSpace') txt_scale = 0.66 - txt = ba.Lstr(resource=self._r + - '.orPunchingSomethingText').evaluate() - ba.textwidget(parent=self._subcontainer, - position=(h, v), - size=(0, 0), - scale=txt_scale, - maxwidth=self._sub_width * 0.9, - text=txt, - h_align='center', - color=paragraph, - v_align='center', - flatness=1.0) - v -= (spacing * 27.0 + - getres(self._r + '.orPunchingSomethingExtraSpace')) + txt = ba.Lstr( + resource=self._r + '.orPunchingSomethingText' + ).evaluate() + ba.textwidget( + parent=self._subcontainer, + position=(h, v), + size=(0, 0), + scale=txt_scale, + maxwidth=self._sub_width * 0.9, + text=txt, + h_align='center', + color=paragraph, + v_align='center', + flatness=1.0, + ) + v -= spacing * 27.0 + getres( + self._r + '.orPunchingSomethingExtraSpace' + ) txt_scale = 1.0 - txt = ba.Lstr(resource=self._r + '.canHelpText', - subs=[('${APP_NAME}', ba.Lstr(resource='titleText')) - ]).evaluate() - ba.textwidget(parent=self._subcontainer, - position=(h, v), - size=(0, 0), - scale=txt_scale, - flatness=1.0, - text=txt, - h_align='center', - color=paragraph, - v_align='center') + txt = ba.Lstr( + resource=self._r + '.canHelpText', + subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))], + ).evaluate() + ba.textwidget( + parent=self._subcontainer, + position=(h, v), + size=(0, 0), + scale=txt_scale, + flatness=1.0, + text=txt, + h_align='center', + color=paragraph, + v_align='center', + ) v -= spacing * 70.0 txt_scale = 1.0 txt = ba.Lstr(resource=self._r + '.toGetTheMostText').evaluate() - ba.textwidget(parent=self._subcontainer, - position=(h, v), - size=(0, 0), - scale=txt_scale, - maxwidth=self._sub_width * 0.9, - text=txt, - h_align='center', - color=header, - v_align='center', - flatness=1.0) + ba.textwidget( + parent=self._subcontainer, + position=(h, v), + size=(0, 0), + scale=txt_scale, + maxwidth=self._sub_width * 0.9, + text=txt, + h_align='center', + color=header, + v_align='center', + flatness=1.0, + ) v -= spacing * 40.0 txt_scale = 0.74 txt = ba.Lstr(resource=self._r + '.friendsText').evaluate() hval2 = h - 220 - ba.textwidget(parent=self._subcontainer, - position=(hval2, v), - size=(0, 0), - scale=txt_scale, - maxwidth=100, - text=txt, - h_align='right', - color=header, - v_align='center', - flatness=1.0) + ba.textwidget( + parent=self._subcontainer, + position=(hval2, v), + size=(0, 0), + scale=txt_scale, + maxwidth=100, + text=txt, + h_align='right', + color=header, + v_align='center', + flatness=1.0, + ) - txt = ba.Lstr(resource=self._r + '.friendsGoodText', - subs=[('${APP_NAME}', ba.Lstr(resource='titleText')) - ]).evaluate() + txt = ba.Lstr( + resource=self._r + '.friendsGoodText', + subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))], + ).evaluate() txt_scale = 0.7 - ba.textwidget(parent=self._subcontainer, - position=(hval2 + 10, v + 8), - size=(0, 0), - scale=txt_scale, - maxwidth=500, - text=txt, - h_align='left', - color=paragraph, - flatness=1.0) + ba.textwidget( + parent=self._subcontainer, + position=(hval2 + 10, v + 8), + size=(0, 0), + scale=txt_scale, + maxwidth=500, + text=txt, + h_align='left', + color=paragraph, + flatness=1.0, + ) app = ba.app v -= spacing * 45.0 - txt = (ba.Lstr(resource=self._r + '.devicesText').evaluate() - if app.vr_mode else ba.Lstr(resource=self._r + - '.controllersText').evaluate()) + txt = ( + ba.Lstr(resource=self._r + '.devicesText').evaluate() + if app.vr_mode + else ba.Lstr(resource=self._r + '.controllersText').evaluate() + ) txt_scale = 0.74 hval2 = h - 220 - ba.textwidget(parent=self._subcontainer, - position=(hval2, v), - size=(0, 0), - scale=txt_scale, - maxwidth=100, - text=txt, - h_align='right', - v_align='center', - color=header, - flatness=1.0) + ba.textwidget( + parent=self._subcontainer, + position=(hval2, v), + size=(0, 0), + scale=txt_scale, + maxwidth=100, + text=txt, + h_align='right', + v_align='center', + color=header, + flatness=1.0, + ) txt_scale = 0.7 if not app.vr_mode: - infotxt = ('.controllersInfoTextRemoteOnly' - if app.iircade_mode else '.controllersInfoText') + infotxt = ( + '.controllersInfoTextRemoteOnly' + if app.iircade_mode + else '.controllersInfoText' + ) txt = ba.Lstr( resource=self._r + infotxt, fallback_resource=self._r + '.controllersInfoText', - subs=[('${APP_NAME}', ba.Lstr(resource='titleText')), - ('${REMOTE_APP_NAME}', get_remote_app_name()) - ]).evaluate() + subs=[ + ('${APP_NAME}', ba.Lstr(resource='titleText')), + ('${REMOTE_APP_NAME}', get_remote_app_name()), + ], + ).evaluate() else: - txt = ba.Lstr(resource=self._r + '.devicesInfoText', - subs=[('${APP_NAME}', - ba.Lstr(resource='titleText')) - ]).evaluate() + txt = ba.Lstr( + resource=self._r + '.devicesInfoText', + subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))], + ).evaluate() - ba.textwidget(parent=self._subcontainer, - position=(hval2 + 10, v + 8), - size=(0, 0), - scale=txt_scale, - maxwidth=500, - max_height=105, - text=txt, - h_align='left', - color=paragraph, - flatness=1.0) + ba.textwidget( + parent=self._subcontainer, + position=(hval2 + 10, v + 8), + size=(0, 0), + scale=txt_scale, + maxwidth=500, + max_height=105, + text=txt, + h_align='left', + color=paragraph, + flatness=1.0, + ) v -= spacing * 150.0 txt = ba.Lstr(resource=self._r + '.controlsText').evaluate() txt_scale = 1.4 txt_maxwidth = 480 - ba.textwidget(parent=self._subcontainer, - position=(h, v), - size=(0, 0), - scale=txt_scale, - flatness=0.5, - text=txt, - h_align='center', - color=header, - v_align='center', - res_scale=1.5, - maxwidth=txt_maxwidth) + ba.textwidget( + parent=self._subcontainer, + position=(h, v), + size=(0, 0), + scale=txt_scale, + flatness=0.5, + text=txt, + h_align='center', + color=header, + v_align='center', + res_scale=1.5, + maxwidth=txt_maxwidth, + ) txt_width = min( txt_maxwidth, - ba.internal.get_string_width(txt, suppress_warning=True) * - txt_scale) + ba.internal.get_string_width(txt, suppress_warning=True) + * txt_scale, + ) icon_size = 70 hval2 = h - (txt_width * 0.5 + icon_size * 0.5 * icon_buffer) - ba.imagewidget(parent=self._subcontainer, - size=(icon_size, icon_size), - position=(hval2 - 0.5 * icon_size, - v - 0.45 * icon_size), - texture=logo_tex) + ba.imagewidget( + parent=self._subcontainer, + size=(icon_size, icon_size), + position=(hval2 - 0.5 * icon_size, v - 0.45 * icon_size), + texture=logo_tex, + ) v -= spacing * 45.0 txt_scale = 0.7 - txt = ba.Lstr(resource=self._r + '.controlsSubtitleText', - subs=[('${APP_NAME}', ba.Lstr(resource='titleText')) - ]).evaluate() - ba.textwidget(parent=self._subcontainer, - position=(h, v), - size=(0, 0), - scale=txt_scale, - maxwidth=self._sub_width * 0.9, - flatness=1.0, - text=txt, - h_align='center', - color=paragraph, - v_align='center') + txt = ba.Lstr( + resource=self._r + '.controlsSubtitleText', + subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))], + ).evaluate() + ba.textwidget( + parent=self._subcontainer, + position=(h, v), + size=(0, 0), + scale=txt_scale, + maxwidth=self._sub_width * 0.9, + flatness=1.0, + text=txt, + h_align='center', + color=paragraph, + v_align='center', + ) v -= spacing * 160.0 sep = 70 @@ -387,142 +469,162 @@ class HelpWindow(ba.Window): # icon_size_2 = 30 hval2 = h - sep vval2 = v - ba.imagewidget(parent=self._subcontainer, - size=(icon_size, icon_size), - position=(hval2 - 0.5 * icon_size, - vval2 - 0.5 * icon_size), - texture=ba.gettexture('buttonPunch'), - color=(1, 0.7, 0.3)) + ba.imagewidget( + parent=self._subcontainer, + size=(icon_size, icon_size), + position=(hval2 - 0.5 * icon_size, vval2 - 0.5 * icon_size), + texture=ba.gettexture('buttonPunch'), + color=(1, 0.7, 0.3), + ) txt_scale = getres(self._r + '.punchInfoTextScale') txt = ba.Lstr(resource=self._r + '.punchInfoText').evaluate() - ba.textwidget(parent=self._subcontainer, - position=(h - sep - 185 + 70, v + 120), - size=(0, 0), - scale=txt_scale, - flatness=1.0, - text=txt, - h_align='center', - color=(1, 0.7, 0.3, 1.0), - v_align='top') + ba.textwidget( + parent=self._subcontainer, + position=(h - sep - 185 + 70, v + 120), + size=(0, 0), + scale=txt_scale, + flatness=1.0, + text=txt, + h_align='center', + color=(1, 0.7, 0.3, 1.0), + v_align='top', + ) hval2 = h + sep vval2 = v - ba.imagewidget(parent=self._subcontainer, - size=(icon_size, icon_size), - position=(hval2 - 0.5 * icon_size, - vval2 - 0.5 * icon_size), - texture=ba.gettexture('buttonBomb'), - color=(1, 0.3, 0.3)) + ba.imagewidget( + parent=self._subcontainer, + size=(icon_size, icon_size), + position=(hval2 - 0.5 * icon_size, vval2 - 0.5 * icon_size), + texture=ba.gettexture('buttonBomb'), + color=(1, 0.3, 0.3), + ) txt = ba.Lstr(resource=self._r + '.bombInfoText').evaluate() txt_scale = getres(self._r + '.bombInfoTextScale') - ba.textwidget(parent=self._subcontainer, - position=(h + sep + 50 + 60, v - 35), - size=(0, 0), - scale=txt_scale, - flatness=1.0, - maxwidth=270, - text=txt, - h_align='center', - color=(1, 0.3, 0.3, 1.0), - v_align='top') + ba.textwidget( + parent=self._subcontainer, + position=(h + sep + 50 + 60, v - 35), + size=(0, 0), + scale=txt_scale, + flatness=1.0, + maxwidth=270, + text=txt, + h_align='center', + color=(1, 0.3, 0.3, 1.0), + v_align='top', + ) hval2 = h vval2 = v + sep - ba.imagewidget(parent=self._subcontainer, - size=(icon_size, icon_size), - position=(hval2 - 0.5 * icon_size, - vval2 - 0.5 * icon_size), - texture=ba.gettexture('buttonPickUp'), - color=(0.5, 0.5, 1)) + ba.imagewidget( + parent=self._subcontainer, + size=(icon_size, icon_size), + position=(hval2 - 0.5 * icon_size, vval2 - 0.5 * icon_size), + texture=ba.gettexture('buttonPickUp'), + color=(0.5, 0.5, 1), + ) txtl = ba.Lstr(resource=self._r + '.pickUpInfoText') txt_scale = getres(self._r + '.pickUpInfoTextScale') - ba.textwidget(parent=self._subcontainer, - position=(h + 60 + 120, v + sep + 50), - size=(0, 0), - scale=txt_scale, - flatness=1.0, - text=txtl, - h_align='center', - color=(0.5, 0.5, 1, 1.0), - v_align='top') + ba.textwidget( + parent=self._subcontainer, + position=(h + 60 + 120, v + sep + 50), + size=(0, 0), + scale=txt_scale, + flatness=1.0, + text=txtl, + h_align='center', + color=(0.5, 0.5, 1, 1.0), + v_align='top', + ) hval2 = h vval2 = v - sep - ba.imagewidget(parent=self._subcontainer, - size=(icon_size, icon_size), - position=(hval2 - 0.5 * icon_size, - vval2 - 0.5 * icon_size), - texture=ba.gettexture('buttonJump'), - color=(0.4, 1, 0.4)) + ba.imagewidget( + parent=self._subcontainer, + size=(icon_size, icon_size), + position=(hval2 - 0.5 * icon_size, vval2 - 0.5 * icon_size), + texture=ba.gettexture('buttonJump'), + color=(0.4, 1, 0.4), + ) txt = ba.Lstr(resource=self._r + '.jumpInfoText').evaluate() txt_scale = getres(self._r + '.jumpInfoTextScale') - ba.textwidget(parent=self._subcontainer, - position=(h - 250 + 75, v - sep - 15 + 30), - size=(0, 0), - scale=txt_scale, - flatness=1.0, - text=txt, - h_align='center', - color=(0.4, 1, 0.4, 1.0), - v_align='top') + ba.textwidget( + parent=self._subcontainer, + position=(h - 250 + 75, v - sep - 15 + 30), + size=(0, 0), + scale=txt_scale, + flatness=1.0, + text=txt, + h_align='center', + color=(0.4, 1, 0.4, 1.0), + v_align='top', + ) txt = ba.Lstr(resource=self._r + '.runInfoText').evaluate() txt_scale = getres(self._r + '.runInfoTextScale') - ba.textwidget(parent=self._subcontainer, - position=(h, v - sep - 100), - size=(0, 0), - scale=txt_scale, - maxwidth=self._sub_width * 0.93, - flatness=1.0, - text=txt, - h_align='center', - color=(0.7, 0.7, 1.0, 1.0), - v_align='center') + ba.textwidget( + parent=self._subcontainer, + position=(h, v - sep - 100), + size=(0, 0), + scale=txt_scale, + maxwidth=self._sub_width * 0.93, + flatness=1.0, + text=txt, + h_align='center', + color=(0.7, 0.7, 1.0, 1.0), + v_align='center', + ) v -= spacing * 280.0 txt = ba.Lstr(resource=self._r + '.powerupsText').evaluate() txt_scale = 1.4 txt_maxwidth = 480 - ba.textwidget(parent=self._subcontainer, - position=(h, v), - size=(0, 0), - scale=txt_scale, - flatness=0.5, - text=txt, - h_align='center', - color=header, - v_align='center', - maxwidth=txt_maxwidth) + ba.textwidget( + parent=self._subcontainer, + position=(h, v), + size=(0, 0), + scale=txt_scale, + flatness=0.5, + text=txt, + h_align='center', + color=header, + v_align='center', + maxwidth=txt_maxwidth, + ) txt_width = min( txt_maxwidth, - ba.internal.get_string_width(txt, suppress_warning=True) * - txt_scale) + ba.internal.get_string_width(txt, suppress_warning=True) + * txt_scale, + ) icon_size = 70 hval2 = h - (txt_width * 0.5 + icon_size * 0.5 * icon_buffer) - ba.imagewidget(parent=self._subcontainer, - size=(icon_size, icon_size), - position=(hval2 - 0.5 * icon_size, - v - 0.45 * icon_size), - texture=logo_tex) + ba.imagewidget( + parent=self._subcontainer, + size=(icon_size, icon_size), + position=(hval2 - 0.5 * icon_size, v - 0.45 * icon_size), + texture=logo_tex, + ) v -= spacing * 50.0 txt_scale = getres(self._r + '.powerupsSubtitleTextScale') txt = ba.Lstr(resource=self._r + '.powerupsSubtitleText').evaluate() - ba.textwidget(parent=self._subcontainer, - position=(h, v), - size=(0, 0), - scale=txt_scale, - maxwidth=self._sub_width * 0.9, - text=txt, - h_align='center', - color=paragraph, - v_align='center', - flatness=1.0) + ba.textwidget( + parent=self._subcontainer, + position=(h, v), + size=(0, 0), + scale=txt_scale, + maxwidth=self._sub_width * 0.9, + text=txt, + h_align='center', + color=paragraph, + v_align='center', + flatness=1.0, + ) v -= spacing * 1.0 @@ -539,9 +641,15 @@ class HelpWindow(ba.Window): shadow_tex = ba.gettexture('shadowSharp') for tex in [ - 'powerupPunch', 'powerupShield', 'powerupBomb', - 'powerupHealth', 'powerupIceBombs', 'powerupImpactBombs', - 'powerupStickyBombs', 'powerupLandMines', 'powerupCurse' + 'powerupPunch', + 'powerupShield', + 'powerupBomb', + 'powerupHealth', + 'powerupIceBombs', + 'powerupImpactBombs', + 'powerupStickyBombs', + 'powerupLandMines', + 'powerupCurse', ]: name = ba.Lstr(resource=self._r + '.' + tex + 'NameText') desc = ba.Lstr(resource=self._r + '.' + tex + 'DescriptionText') @@ -551,48 +659,59 @@ class HelpWindow(ba.Window): ba.imagewidget( parent=self._subcontainer, size=(shadow_size, shadow_size), - position=(h + mm1 + shadow_offs_x - 0.5 * shadow_size, - v + shadow_offs_y - 0.5 * shadow_size), + position=( + h + mm1 + shadow_offs_x - 0.5 * shadow_size, + v + shadow_offs_y - 0.5 * shadow_size, + ), texture=shadow_tex, color=(0, 0, 0), - opacity=0.5) - ba.imagewidget(parent=self._subcontainer, - size=(icon_size, icon_size), - position=(h + mm1 - 0.5 * icon_size, - v - 0.5 * icon_size), - texture=ba.gettexture(tex)) + opacity=0.5, + ) + ba.imagewidget( + parent=self._subcontainer, + size=(icon_size, icon_size), + position=(h + mm1 - 0.5 * icon_size, v - 0.5 * icon_size), + texture=ba.gettexture(tex), + ) txt_scale = t_big txtl = name - ba.textwidget(parent=self._subcontainer, - position=(h + mm2, v + 3), - size=(0, 0), - scale=txt_scale, - maxwidth=200, - flatness=1.0, - text=txtl, - h_align='left', - color=header2, - v_align='center') + ba.textwidget( + parent=self._subcontainer, + position=(h + mm2, v + 3), + size=(0, 0), + scale=txt_scale, + maxwidth=200, + flatness=1.0, + text=txtl, + h_align='left', + color=header2, + v_align='center', + ) txt_scale = t_small txtl = desc - ba.textwidget(parent=self._subcontainer, - position=(h + mm3, v), - size=(0, 0), - scale=txt_scale, - maxwidth=300, - flatness=1.0, - text=txtl, - h_align='left', - color=paragraph, - v_align='center', - res_scale=0.5) + ba.textwidget( + parent=self._subcontainer, + position=(h + mm3, v), + size=(0, 0), + scale=txt_scale, + maxwidth=300, + flatness=1.0, + text=txtl, + h_align='left', + color=paragraph, + v_align='center', + res_scale=0.5, + ) def _close(self) -> None: # pylint: disable=cyclic-import from bastd.ui.mainmenu import MainMenuWindow - ba.containerwidget(edit=self._root_widget, - transition=self._transition_out) + + ba.containerwidget( + edit=self._root_widget, transition=self._transition_out + ) if self._main_menu: ba.app.ui.set_main_menu_window( - MainMenuWindow(transition='in_left').get_root_widget()) + MainMenuWindow(transition='in_left').get_root_widget() + ) diff --git a/dist/ba_data/python/bastd/ui/iconpicker.py b/dist/ba_data/python/bastd/ui/iconpicker.py index dbe109b..bcbac25 100644 --- a/dist/ba_data/python/bastd/ui/iconpicker.py +++ b/dist/ba_data/python/bastd/ui/iconpicker.py @@ -18,29 +18,37 @@ if TYPE_CHECKING: class IconPicker(popup.PopupWindow): """Picker for icons.""" - def __init__(self, - parent: ba.Widget, - position: tuple[float, float] = (0.0, 0.0), - delegate: Any = None, - scale: float | None = None, - offset: tuple[float, float] = (0.0, 0.0), - tint_color: Sequence[float] = (1.0, 1.0, 1.0), - tint2_color: Sequence[float] = (1.0, 1.0, 1.0), - selected_icon: str | None = None): + def __init__( + self, + parent: ba.Widget, + position: tuple[float, float] = (0.0, 0.0), + delegate: Any = None, + scale: float | None = None, + offset: tuple[float, float] = (0.0, 0.0), + tint_color: Sequence[float] = (1.0, 1.0, 1.0), + tint2_color: Sequence[float] = (1.0, 1.0, 1.0), + selected_icon: str | None = None, + ): # pylint: disable=too-many-locals del parent # unused here del tint_color # unused_here del tint2_color # unused here uiscale = ba.app.ui.uiscale if scale is None: - scale = (1.85 if uiscale is ba.UIScale.SMALL else - 1.65 if uiscale is ba.UIScale.MEDIUM else 1.23) + scale = ( + 1.85 + if uiscale is ba.UIScale.SMALL + else 1.65 + if uiscale is ba.UIScale.MEDIUM + else 1.23 + ) self._delegate = delegate self._transitioning_out = False - self._icons = [ba.charstr(ba.SpecialChar.LOGO) - ] + ba.app.accounts_v1.get_purchased_icons() + self._icons = [ + ba.charstr(ba.SpecialChar.LOGO) + ] + ba.app.accounts_v1.get_purchased_icons() count = len(self._icons) columns = 4 rows = int(math.ceil(float(count) / columns)) @@ -50,73 +58,89 @@ class IconPicker(popup.PopupWindow): button_buffer_h = 10 button_buffer_v = 5 - self._width = (10 + columns * (button_width + 2 * button_buffer_h) * - (1.0 / 0.95) * (1.0 / 0.8)) - self._height = (self._width * - (0.8 if uiscale is ba.UIScale.SMALL else 1.06)) + self._width = 10 + columns * (button_width + 2 * button_buffer_h) * ( + 1.0 / 0.95 + ) * (1.0 / 0.8) + self._height = self._width * ( + 0.8 if uiscale is ba.UIScale.SMALL else 1.06 + ) self._scroll_width = self._width * 0.8 self._scroll_height = self._height * 0.8 - self._scroll_position = ((self._width - self._scroll_width) * 0.5, - (self._height - self._scroll_height) * 0.5) + self._scroll_position = ( + (self._width - self._scroll_width) * 0.5, + (self._height - self._scroll_height) * 0.5, + ) # creates our _root_widget - popup.PopupWindow.__init__(self, - position=position, - size=(self._width, self._height), - scale=scale, - bg_color=(0.5, 0.5, 0.5), - offset=offset, - focus_position=self._scroll_position, - focus_size=(self._scroll_width, - self._scroll_height)) + popup.PopupWindow.__init__( + self, + position=position, + size=(self._width, self._height), + scale=scale, + bg_color=(0.5, 0.5, 0.5), + offset=offset, + focus_position=self._scroll_position, + focus_size=(self._scroll_width, self._scroll_height), + ) - self._scrollwidget = ba.scrollwidget(parent=self.root_widget, - size=(self._scroll_width, - self._scroll_height), - color=(0.55, 0.55, 0.55), - highlight=False, - position=self._scroll_position) + self._scrollwidget = ba.scrollwidget( + parent=self.root_widget, + size=(self._scroll_width, self._scroll_height), + color=(0.55, 0.55, 0.55), + highlight=False, + position=self._scroll_position, + ) ba.containerwidget(edit=self._scrollwidget, claims_left_right=True) self._sub_width = self._scroll_width * 0.95 - self._sub_height = 5 + rows * (button_height + - 2 * button_buffer_v) + 100 - self._subcontainer = ba.containerwidget(parent=self._scrollwidget, - size=(self._sub_width, - self._sub_height), - background=False) + self._sub_height = ( + 5 + rows * (button_height + 2 * button_buffer_v) + 100 + ) + self._subcontainer = ba.containerwidget( + parent=self._scrollwidget, + size=(self._sub_width, self._sub_height), + background=False, + ) index = 0 for y in range(rows): for x in range(columns): - pos = (x * (button_width + 2 * button_buffer_h) + - button_buffer_h, self._sub_height - (y + 1) * - (button_height + 2 * button_buffer_v) + 0) - btn = ba.buttonwidget(parent=self._subcontainer, - button_type='square', - size=(button_width, button_height), - autoselect=True, - text_scale=1.2, - label='', - color=(0.65, 0.65, 0.65), - on_activate_call=ba.Call( - self._select_icon, - self._icons[index]), - position=pos) - ba.textwidget(parent=self._subcontainer, - h_align='center', - v_align='center', - size=(0, 0), - position=(pos[0] + 0.5 * button_width - 1, - pos[1] + 15), - draw_controller=btn, - text=self._icons[index], - scale=1.8) + pos = ( + x * (button_width + 2 * button_buffer_h) + button_buffer_h, + self._sub_height + - (y + 1) * (button_height + 2 * button_buffer_v) + + 0, + ) + btn = ba.buttonwidget( + parent=self._subcontainer, + button_type='square', + size=(button_width, button_height), + autoselect=True, + text_scale=1.2, + label='', + color=(0.65, 0.65, 0.65), + on_activate_call=ba.Call( + self._select_icon, self._icons[index] + ), + position=pos, + ) + ba.textwidget( + parent=self._subcontainer, + h_align='center', + v_align='center', + size=(0, 0), + position=(pos[0] + 0.5 * button_width - 1, pos[1] + 15), + draw_controller=btn, + text=self._icons[index], + scale=1.8, + ) ba.widget(edit=btn, show_buffer_top=60, show_buffer_bottom=60) if self._icons[index] == selected_icon: - ba.containerwidget(edit=self._subcontainer, - selected_child=btn, - visible_child=btn) + ba.containerwidget( + edit=self._subcontainer, + selected_child=btn, + visible_child=btn, + ) index += 1 if index >= count: @@ -131,19 +155,23 @@ class IconPicker(popup.PopupWindow): on_activate_call=self._on_store_press, color=(0.6, 0.6, 0.6), textcolor=(0.8, 0.8, 0.8), - autoselect=True) + autoselect=True, + ) ba.widget(edit=btn, show_buffer_top=30, show_buffer_bottom=30) def _on_store_press(self) -> None: from bastd.ui.account import show_sign_in_prompt from bastd.ui.store.browser import StoreBrowserWindow + if ba.internal.get_v1_account_state() != 'signed_in': show_sign_in_prompt() return self._transition_out() - StoreBrowserWindow(modal=True, - show_tab=StoreBrowserWindow.TabID.ICONS, - origin_widget=self._get_more_icons_button) + StoreBrowserWindow( + modal=True, + show_tab=StoreBrowserWindow.TabID.ICONS, + origin_widget=self._get_more_icons_button, + ) def _select_icon(self, icon: str) -> None: if self._delegate is not None: diff --git a/dist/ba_data/python/bastd/ui/kiosk.py b/dist/ba_data/python/bastd/ui/kiosk.py index 73fadc9..ae041f4 100644 --- a/dist/ba_data/python/bastd/ui/kiosk.py +++ b/dist/ba_data/python/bastd/ui/kiosk.py @@ -19,6 +19,7 @@ class KioskWindow(ba.Window): def __init__(self, transition: str = 'in_right'): # pylint: disable=too-many-locals, too-many-statements from bastd.ui.confirm import QuitWindow + self._width = 720.0 self._height = 340.0 @@ -26,11 +27,14 @@ class KioskWindow(ba.Window): QuitWindow(swish=True, back=True) super().__init__( - root_widget=ba.containerwidget(size=(self._width, self._height), - transition=transition, - on_cancel_call=_do_cancel, - background=False, - stack_offset=(0, -130))) + root_widget=ba.containerwidget( + size=(self._width, self._height), + transition=transition, + on_cancel_call=_do_cancel, + background=False, + stack_offset=(0, -130), + ) + ) self._r = 'kioskWindow' @@ -73,7 +77,8 @@ class KioskWindow(ba.Window): scale=1.2, h_align='center', v_align='center', - shadow=1.0) + shadow=1.0, + ) else: tdelay = t_delay_base + t_delay_scale * 0.7 ba.textwidget( @@ -81,104 +86,125 @@ class KioskWindow(ba.Window): size=(0, 0), position=(self._width * 0.5, self._height + y_extra - 34), transition_delay=tdelay, - text=(ba.Lstr(resource='demoText', - fallback_resource='mainMenu.demoMenuText') - if ba.app.demo_mode else 'ARCADE'), + text=( + ba.Lstr( + resource='demoText', + fallback_resource='mainMenu.demoMenuText', + ) + if ba.app.demo_mode + else 'ARCADE' + ), flatness=1.0, scale=1.2, h_align='center', v_align='center', - shadow=1.0) + shadow=1.0, + ) h = self._width * 0.5 - b_space tdelay = t_delay_base + t_delay_scale * 0.7 - self._b1 = btn = ba.buttonwidget(parent=self._root_widget, - autoselect=True, - size=(b_width, b_height), - on_activate_call=ba.Call( - self._do_game, 'easy'), - transition_delay=tdelay, - position=(h - b_width * 0.5, b_v), - label='', - button_type='square') - ba.textwidget(parent=self._root_widget, - draw_controller=btn, - transition_delay=tdelay, - size=(0, 0), - position=(h, label_height), - maxwidth=b_width * 0.7, - text=ba.Lstr(resource=self._r + '.easyText'), - scale=1.3, - h_align='center', - v_align='center') - ba.imagewidget(parent=self._root_widget, - draw_controller=btn, - size=(img_width, 0.5 * img_width), - transition_delay=tdelay, - position=(h - img_width * 0.5, img_v), - texture=ba.gettexture('doomShroomPreview'), - model_opaque=model_opaque, - model_transparent=model_transparent, - mask_texture=mask_tex) + self._b1 = btn = ba.buttonwidget( + parent=self._root_widget, + autoselect=True, + size=(b_width, b_height), + on_activate_call=ba.Call(self._do_game, 'easy'), + transition_delay=tdelay, + position=(h - b_width * 0.5, b_v), + label='', + button_type='square', + ) + ba.textwidget( + parent=self._root_widget, + draw_controller=btn, + transition_delay=tdelay, + size=(0, 0), + position=(h, label_height), + maxwidth=b_width * 0.7, + text=ba.Lstr(resource=self._r + '.easyText'), + scale=1.3, + h_align='center', + v_align='center', + ) + ba.imagewidget( + parent=self._root_widget, + draw_controller=btn, + size=(img_width, 0.5 * img_width), + transition_delay=tdelay, + position=(h - img_width * 0.5, img_v), + texture=ba.gettexture('doomShroomPreview'), + model_opaque=model_opaque, + model_transparent=model_transparent, + mask_texture=mask_tex, + ) h = self._width * 0.5 tdelay = t_delay_base + t_delay_scale * 0.65 - self._b2 = btn = ba.buttonwidget(parent=self._root_widget, - autoselect=True, - size=(b_width, b_height), - on_activate_call=ba.Call( - self._do_game, 'medium'), - position=(h - b_width * 0.5, b_v), - label='', - button_type='square', - transition_delay=tdelay) - ba.textwidget(parent=self._root_widget, - draw_controller=btn, - transition_delay=tdelay, - size=(0, 0), - position=(h, label_height), - maxwidth=b_width * 0.7, - text=ba.Lstr(resource=self._r + '.mediumText'), - scale=1.3, - h_align='center', - v_align='center') - ba.imagewidget(parent=self._root_widget, - draw_controller=btn, - size=(img_width, 0.5 * img_width), - transition_delay=tdelay, - position=(h - img_width * 0.5, img_v), - texture=ba.gettexture('footballStadiumPreview'), - model_opaque=model_opaque, - model_transparent=model_transparent, - mask_texture=mask_tex) + self._b2 = btn = ba.buttonwidget( + parent=self._root_widget, + autoselect=True, + size=(b_width, b_height), + on_activate_call=ba.Call(self._do_game, 'medium'), + position=(h - b_width * 0.5, b_v), + label='', + button_type='square', + transition_delay=tdelay, + ) + ba.textwidget( + parent=self._root_widget, + draw_controller=btn, + transition_delay=tdelay, + size=(0, 0), + position=(h, label_height), + maxwidth=b_width * 0.7, + text=ba.Lstr(resource=self._r + '.mediumText'), + scale=1.3, + h_align='center', + v_align='center', + ) + ba.imagewidget( + parent=self._root_widget, + draw_controller=btn, + size=(img_width, 0.5 * img_width), + transition_delay=tdelay, + position=(h - img_width * 0.5, img_v), + texture=ba.gettexture('footballStadiumPreview'), + model_opaque=model_opaque, + model_transparent=model_transparent, + mask_texture=mask_tex, + ) h = self._width * 0.5 + b_space tdelay = t_delay_base + t_delay_scale * 0.6 - self._b3 = btn = ba.buttonwidget(parent=self._root_widget, - autoselect=True, - size=(b_width, b_height), - on_activate_call=ba.Call( - self._do_game, 'hard'), - transition_delay=tdelay, - position=(h - b_width * 0.5, b_v), - label='', - button_type='square') - ba.textwidget(parent=self._root_widget, - draw_controller=btn, - transition_delay=tdelay, - size=(0, 0), - position=(h, label_height), - maxwidth=b_width * 0.7, - text='Hard', - scale=1.3, - h_align='center', - v_align='center') - ba.imagewidget(parent=self._root_widget, - draw_controller=btn, - transition_delay=tdelay, - size=(img_width, 0.5 * img_width), - position=(h - img_width * 0.5, img_v), - texture=ba.gettexture('courtyardPreview'), - model_opaque=model_opaque, - model_transparent=model_transparent, - mask_texture=mask_tex) + self._b3 = btn = ba.buttonwidget( + parent=self._root_widget, + autoselect=True, + size=(b_width, b_height), + on_activate_call=ba.Call(self._do_game, 'hard'), + transition_delay=tdelay, + position=(h - b_width * 0.5, b_v), + label='', + button_type='square', + ) + ba.textwidget( + parent=self._root_widget, + draw_controller=btn, + transition_delay=tdelay, + size=(0, 0), + position=(h, label_height), + maxwidth=b_width * 0.7, + text='Hard', + scale=1.3, + h_align='center', + v_align='center', + ) + ba.imagewidget( + parent=self._root_widget, + draw_controller=btn, + transition_delay=tdelay, + size=(img_width, 0.5 * img_width), + position=(h - img_width * 0.5, img_v), + texture=ba.gettexture('courtyardPreview'), + model_opaque=model_opaque, + model_transparent=model_transparent, + mask_texture=mask_tex, + ) if not ba.app.did_menu_intro: ba.app.did_menu_intro = True @@ -197,99 +223,114 @@ class KioskWindow(ba.Window): scale=1.2, h_align='center', v_align='center', - shadow=1.0) + shadow=1.0, + ) h = self._width * 0.5 - b_space tdelay = t_delay_base + t_delay_scale * 0.7 - self._b4 = btn = ba.buttonwidget(parent=self._root_widget, - autoselect=True, - size=(b_width, b_height), - on_activate_call=ba.Call( - self._do_game, 'ctf'), - transition_delay=tdelay, - position=(h - b_width * 0.5, b_v), - label='', - button_type='square') - ba.textwidget(parent=self._root_widget, - draw_controller=btn, - transition_delay=tdelay, - size=(0, 0), - position=(h, label_height), - maxwidth=b_width * 0.7, - text=ba.Lstr(translate=('gameNames', - 'Capture the Flag')), - scale=1.3, - h_align='center', - v_align='center') - ba.imagewidget(parent=self._root_widget, - draw_controller=btn, - size=(img_width, 0.5 * img_width), - transition_delay=tdelay, - position=(h - img_width * 0.5, img_v), - texture=ba.gettexture('bridgitPreview'), - model_opaque=model_opaque, - model_transparent=model_transparent, - mask_texture=mask_tex) + self._b4 = btn = ba.buttonwidget( + parent=self._root_widget, + autoselect=True, + size=(b_width, b_height), + on_activate_call=ba.Call(self._do_game, 'ctf'), + transition_delay=tdelay, + position=(h - b_width * 0.5, b_v), + label='', + button_type='square', + ) + ba.textwidget( + parent=self._root_widget, + draw_controller=btn, + transition_delay=tdelay, + size=(0, 0), + position=(h, label_height), + maxwidth=b_width * 0.7, + text=ba.Lstr(translate=('gameNames', 'Capture the Flag')), + scale=1.3, + h_align='center', + v_align='center', + ) + ba.imagewidget( + parent=self._root_widget, + draw_controller=btn, + size=(img_width, 0.5 * img_width), + transition_delay=tdelay, + position=(h - img_width * 0.5, img_v), + texture=ba.gettexture('bridgitPreview'), + model_opaque=model_opaque, + model_transparent=model_transparent, + mask_texture=mask_tex, + ) h = self._width * 0.5 tdelay = t_delay_base + t_delay_scale * 0.65 - self._b5 = btn = ba.buttonwidget(parent=self._root_widget, - autoselect=True, - size=(b_width, b_height), - on_activate_call=ba.Call( - self._do_game, 'hockey'), - position=(h - b_width * 0.5, b_v), - label='', - button_type='square', - transition_delay=tdelay) - ba.textwidget(parent=self._root_widget, - draw_controller=btn, - transition_delay=tdelay, - size=(0, 0), - position=(h, label_height), - maxwidth=b_width * 0.7, - text=ba.Lstr(translate=('gameNames', 'Hockey')), - scale=1.3, - h_align='center', - v_align='center') - ba.imagewidget(parent=self._root_widget, - draw_controller=btn, - size=(img_width, 0.5 * img_width), - transition_delay=tdelay, - position=(h - img_width * 0.5, img_v), - texture=ba.gettexture('hockeyStadiumPreview'), - model_opaque=model_opaque, - model_transparent=model_transparent, - mask_texture=mask_tex) + self._b5 = btn = ba.buttonwidget( + parent=self._root_widget, + autoselect=True, + size=(b_width, b_height), + on_activate_call=ba.Call(self._do_game, 'hockey'), + position=(h - b_width * 0.5, b_v), + label='', + button_type='square', + transition_delay=tdelay, + ) + ba.textwidget( + parent=self._root_widget, + draw_controller=btn, + transition_delay=tdelay, + size=(0, 0), + position=(h, label_height), + maxwidth=b_width * 0.7, + text=ba.Lstr(translate=('gameNames', 'Hockey')), + scale=1.3, + h_align='center', + v_align='center', + ) + ba.imagewidget( + parent=self._root_widget, + draw_controller=btn, + size=(img_width, 0.5 * img_width), + transition_delay=tdelay, + position=(h - img_width * 0.5, img_v), + texture=ba.gettexture('hockeyStadiumPreview'), + model_opaque=model_opaque, + model_transparent=model_transparent, + mask_texture=mask_tex, + ) h = self._width * 0.5 + b_space tdelay = t_delay_base + t_delay_scale * 0.6 - self._b6 = btn = ba.buttonwidget(parent=self._root_widget, - autoselect=True, - size=(b_width, b_height), - on_activate_call=ba.Call( - self._do_game, 'epic'), - transition_delay=tdelay, - position=(h - b_width * 0.5, b_v), - label='', - button_type='square') - ba.textwidget(parent=self._root_widget, - draw_controller=btn, - transition_delay=tdelay, - size=(0, 0), - position=(h, label_height), - maxwidth=b_width * 0.7, - text=ba.Lstr(resource=self._r + '.epicModeText'), - scale=1.3, - h_align='center', - v_align='center') - ba.imagewidget(parent=self._root_widget, - draw_controller=btn, - transition_delay=tdelay, - size=(img_width, 0.5 * img_width), - position=(h - img_width * 0.5, img_v), - texture=ba.gettexture('tipTopPreview'), - model_opaque=model_opaque, - model_transparent=model_transparent, - mask_texture=mask_tex) + self._b6 = btn = ba.buttonwidget( + parent=self._root_widget, + autoselect=True, + size=(b_width, b_height), + on_activate_call=ba.Call(self._do_game, 'epic'), + transition_delay=tdelay, + position=(h - b_width * 0.5, b_v), + label='', + button_type='square', + ) + ba.textwidget( + parent=self._root_widget, + draw_controller=btn, + transition_delay=tdelay, + size=(0, 0), + position=(h, label_height), + maxwidth=b_width * 0.7, + text=ba.Lstr(resource=self._r + '.epicModeText'), + scale=1.3, + h_align='center', + v_align='center', + ) + ba.imagewidget( + parent=self._root_widget, + draw_controller=btn, + transition_delay=tdelay, + size=(img_width, 0.5 * img_width), + position=(h - img_width * 0.5, img_v), + texture=ba.gettexture('tipTopPreview'), + model_opaque=model_opaque, + model_transparent=model_transparent, + mask_texture=mask_tex, + ) else: self._b4 = self._b5 = self._b6 = None @@ -305,15 +346,18 @@ class KioskWindow(ba.Window): position=(self._width * 0.5 - 60.0, b_v - 70.0), transition_delay=tdelay, label=ba.Lstr(resource=self._r + '.fullMenuText'), - on_activate_call=self._do_full_menu) + on_activate_call=self._do_full_menu, + ) else: self._b7 = None self._restore_state() self._update() - self._update_timer = ba.Timer(1.0, - ba.WeakCall(self._update), - timetype=ba.TimeType.REAL, - repeat=True) + self._update_timer = ba.Timer( + 1.0, + ba.WeakCall(self._update), + timetype=ba.TimeType.REAL, + repeat=True, + ) def _restore_state(self) -> None: sel_name = ba.app.ui.window_states.get(type(self)) @@ -379,63 +423,79 @@ class KioskWindow(ba.Window): appconfig['Free-for-All Playlists'] = {} appconfig['Show Tutorial'] = False if mode == 'epic': - appconfig['Free-for-All Playlists']['Just Epic Elim'] = [{ - 'settings': { - 'Epic Mode': 1, - 'Lives Per Player': 1, - 'Respawn Times': 1.0, - 'Time Limit': 0, - 'map': 'Tip Top' - }, - 'type': 'bs_elimination.EliminationGame' - }] + appconfig['Free-for-All Playlists']['Just Epic Elim'] = [ + { + 'settings': { + 'Epic Mode': 1, + 'Lives Per Player': 1, + 'Respawn Times': 1.0, + 'Time Limit': 0, + 'map': 'Tip Top', + }, + 'type': 'bs_elimination.EliminationGame', + } + ] appconfig['Free-for-All Playlist Selection'] = 'Just Epic Elim' - ba.internal.fade_screen(False, - endcall=ba.Call( - ba.pushcall, - ba.Call( - ba.internal.new_host_session, - ba.FreeForAllSession))) + ba.internal.fade_screen( + False, + endcall=ba.Call( + ba.pushcall, + ba.Call( + ba.internal.new_host_session, ba.FreeForAllSession + ), + ), + ) else: if mode == 'ctf': - appconfig['Team Tournament Playlists']['Just CTF'] = [{ - 'settings': { - 'Epic Mode': False, - 'Flag Idle Return Time': 30, - 'Flag Touch Return Time': 0, - 'Respawn Times': 1.0, - 'Score to Win': 3, - 'Time Limit': 0, - 'map': 'Bridgit' - }, - 'type': 'bs_capture_the_flag.CTFGame' - }] - appconfig[ - 'Team Tournament Playlist Selection'] = 'Just CTF' + appconfig['Team Tournament Playlists']['Just CTF'] = [ + { + 'settings': { + 'Epic Mode': False, + 'Flag Idle Return Time': 30, + 'Flag Touch Return Time': 0, + 'Respawn Times': 1.0, + 'Score to Win': 3, + 'Time Limit': 0, + 'map': 'Bridgit', + }, + 'type': 'bs_capture_the_flag.CTFGame', + } + ] + appconfig['Team Tournament Playlist Selection'] = 'Just CTF' else: - appconfig['Team Tournament Playlists']['Just Hockey'] = [{ - 'settings': { - 'Respawn Times': 1.0, - 'Score to Win': 1, - 'Time Limit': 0, - 'map': 'Hockey Stadium' - }, - 'type': 'bs_hockey.HockeyGame' - }] - appconfig['Team Tournament Playlist Selection'] = ( - 'Just Hockey') - ba.internal.fade_screen(False, - endcall=ba.Call( - ba.pushcall, - ba.Call( - ba.internal.new_host_session, - ba.DualTeamSession))) + appconfig['Team Tournament Playlists']['Just Hockey'] = [ + { + 'settings': { + 'Respawn Times': 1.0, + 'Score to Win': 1, + 'Time Limit': 0, + 'map': 'Hockey Stadium', + }, + 'type': 'bs_hockey.HockeyGame', + } + ] + appconfig[ + 'Team Tournament Playlist Selection' + ] = 'Just Hockey' + ba.internal.fade_screen( + False, + endcall=ba.Call( + ba.pushcall, + ba.Call( + ba.internal.new_host_session, ba.DualTeamSession + ), + ), + ) ba.containerwidget(edit=self._root_widget, transition='out_left') return - game = ('Easy:Onslaught Training' - if mode == 'easy' else 'Easy:Rookie Football' - if mode == 'medium' else 'Easy:Uber Onslaught') + game = ( + 'Easy:Onslaught Training' + if mode == 'easy' + else 'Easy:Rookie Football' + if mode == 'medium' + else 'Easy:Uber Onslaught' + ) cfg = ba.app.config cfg['Selected Coop Game'] = game cfg.commit() @@ -444,6 +504,7 @@ class KioskWindow(ba.Window): def _do_full_menu(self) -> None: from bastd.ui.mainmenu import MainMenuWindow + self._save_state() ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.did_menu_intro = True # prevent delayed transition-in diff --git a/dist/ba_data/python/bastd/ui/league/rankbutton.py b/dist/ba_data/python/bastd/ui/league/rankbutton.py index a679b8a..ce8166a 100644 --- a/dist/ba_data/python/bastd/ui/league/rankbutton.py +++ b/dist/ba_data/python/bastd/ui/league/rankbutton.py @@ -16,16 +16,18 @@ if TYPE_CHECKING: class LeagueRankButton: """Button showing league rank.""" - def __init__(self, - parent: ba.Widget, - position: tuple[float, float], - size: tuple[float, float], - scale: float, - on_activate_call: Callable[[], Any] | None = None, - transition_delay: float | None = None, - color: tuple[float, float, float] | None = None, - textcolor: tuple[float, float, float] | None = None, - smooth_update_delay: float | None = None): + def __init__( + self, + parent: ba.Widget, + position: tuple[float, float], + size: tuple[float, float], + scale: float, + on_activate_call: Callable[[], Any] | None = None, + transition_delay: float | None = None, + color: tuple[float, float, float] | None = None, + textcolor: tuple[float, float, float] | None = None, + smooth_update_delay: float | None = None, + ): if on_activate_call is None: on_activate_call = ba.WeakCall(self._default_on_activate_call) self._on_activate_call = on_activate_call @@ -44,15 +46,17 @@ class LeagueRankButton: self._parent = parent self._position: tuple[float, float] = (0.0, 0.0) - self._button = ba.buttonwidget(parent=parent, - size=size, - label='', - button_type='square', - scale=scale, - autoselect=True, - on_activate_call=self._on_activate, - transition_delay=transition_delay, - color=color) + self._button = ba.buttonwidget( + parent=parent, + size=size, + label='', + button_type='square', + scale=scale, + autoselect=True, + on_activate_call=self._on_activate, + transition_delay=transition_delay, + color=color, + ) self._title_text = ba.textwidget( parent=parent, @@ -63,24 +67,28 @@ class LeagueRankButton: maxwidth=size[0] * scale * 0.85, text=ba.Lstr( resource='league.leagueRankText', - fallback_resource='coopSelectWindow.powerRankingText'), + fallback_resource='coopSelectWindow.powerRankingText', + ), color=self._header_color, flatness=1.0, shadow=1.0, scale=scale * 0.5, - transition_delay=transition_delay) + transition_delay=transition_delay, + ) - self._value_text = ba.textwidget(parent=parent, - size=(0, 0), - h_align='center', - v_align='center', - maxwidth=size[0] * scale * 0.85, - text='-', - draw_controller=self._button, - big=True, - scale=scale, - transition_delay=transition_delay, - color=textcolor) + self._value_text = ba.textwidget( + parent=parent, + size=(0, 0), + h_align='center', + v_align='center', + maxwidth=size[0] * scale * 0.85, + text='-', + draw_controller=self._button, + big=True, + scale=scale, + transition_delay=transition_delay, + color=textcolor, + ) self._smooth_percent: float | None = None self._percent: int | None = None @@ -99,10 +107,12 @@ class LeagueRankButton: self._doing_power_ranking_query = False self.set_position(position) self._bg_flash = False - self._update_timer = ba.Timer(1.0, - ba.WeakCall(self._update), - timetype=ba.TimeType.REAL, - repeat=True) + self._update_timer = ba.Timer( + 1.0, + ba.WeakCall(self._update), + timetype=ba.TimeType.REAL, + repeat=True, + ) self._update() # If we've got cached power-ranking data already, apply it. @@ -119,10 +129,12 @@ class LeagueRankButton: self._ticking_node.delete() def _start_smooth_update(self) -> None: - self._smooth_update_timer = ba.Timer(0.05, - ba.WeakCall(self._smooth_update), - repeat=True, - timetype=ba.TimeType.REAL) + self._smooth_update_timer = ba.Timer( + 0.05, + ba.WeakCall(self._smooth_update), + repeat=True, + timetype=ba.TimeType.REAL, + ) def _smooth_update(self) -> None: # pylint: disable=too-many-branches @@ -136,24 +148,28 @@ class LeagueRankButton: 'sound', attrs={ 'sound': ba.getsound('scoreIncrease'), - 'positional': False - }) - self._bg_flash = (not self._bg_flash) - color_used = ((self._color[0] * 2, self._color[1] * 2, - self._color[2] * - 2) if self._bg_flash else self._color) - textcolor_used = ((1, 1, 1) if self._bg_flash else self._textcolor) - header_color_used = ((1, 1, - 1) if self._bg_flash else self._header_color) + 'positional': False, + }, + ) + self._bg_flash = not self._bg_flash + color_used = ( + (self._color[0] * 2, self._color[1] * 2, self._color[2] * 2) + if self._bg_flash + else self._color + ) + textcolor_used = (1, 1, 1) if self._bg_flash else self._textcolor + header_color_used = ( + (1, 1, 1) if self._bg_flash else self._header_color + ) if self._rank is not None: assert self._smooth_rank is not None self._smooth_rank -= 1.0 * self._smooth_increase_speed - finished = (int(self._smooth_rank) <= self._rank) + finished = int(self._smooth_rank) <= self._rank elif self._smooth_percent is not None: self._smooth_percent += 1.0 * self._smooth_increase_speed assert self._percent is not None - finished = (int(self._smooth_percent) >= self._percent) + finished = int(self._smooth_percent) >= self._percent else: finished = True if finished: @@ -175,35 +191,39 @@ class LeagueRankButton: h_align='center', v_align='center', text='+' + self._improvement_text + '!', - position=(self._position[0] + - self._size[0] * 0.5 * self._scale, - self._position[1] + - self._size[1] * -0.2 * self._scale), + position=( + self._position[0] + self._size[0] * 0.5 * self._scale, + self._position[1] + self._size[1] * -0.2 * self._scale, + ), color=(0, 1, 0), flatness=1.0, shadow=0.0, - scale=self._scale * 0.7) + scale=self._scale * 0.7, + ) def safe_delete(widget: ba.Widget) -> None: if widget: widget.delete() - ba.timer(2.0, - ba.Call(safe_delete, diff_text), - timetype=ba.TimeType.REAL) + ba.timer( + 2.0, + ba.Call(safe_delete, diff_text), + timetype=ba.TimeType.REAL, + ) status_text: str | ba.Lstr if self._rank is not None: assert self._smooth_rank is not None - status_text = ba.Lstr(resource='numberText', - subs=[('${NUMBER}', - str(int(self._smooth_rank)))]) + status_text = ba.Lstr( + resource='numberText', + subs=[('${NUMBER}', str(int(self._smooth_rank)))], + ) elif self._smooth_percent is not None: status_text = str(int(self._smooth_percent)) + '%' else: status_text = '-' - ba.textwidget(edit=self._value_text, - text=status_text, - color=textcolor_used) + ba.textwidget( + edit=self._value_text, text=status_text, color=textcolor_used + ) ba.textwidget(edit=self._title_text, color=header_color_used) ba.buttonwidget(edit=self._button, color=color_used) @@ -211,8 +231,7 @@ class LeagueRankButton: ba.print_exception('Error doing smooth update.') self._smooth_update_timer = None - def _update_for_league_rank_data(self, - data: dict[str, Any] | None) -> None: + def _update_for_league_rank_data(self, data: dict[str, Any] | None) -> None: # pylint: disable=too-many-branches # pylint: disable=too-many-statements @@ -236,20 +255,23 @@ class LeagueRankButton: # If this is the first set, league has changed, or rank has gotten # worse, snap the smooth value immediately. assert self._rank is not None - if (self._smooth_rank is None or prev_league != self._league - or self._rank > int(self._smooth_rank)): + if ( + self._smooth_rank is None + or prev_league != self._league + or self._rank > int(self._smooth_rank) + ): self._smooth_rank = float(self._rank) - status_text = ba.Lstr(resource='numberText', - subs=[('${NUMBER}', - str(int(self._smooth_rank)))]) + status_text = ba.Lstr( + resource='numberText', + subs=[('${NUMBER}', str(int(self._smooth_rank)))], + ) else: try: if not data['scores'] or data['scores'][-1][1] <= 0: self._percent = self._rank = None status_text = '-' else: - our_points = ba.app.accounts_v1.get_league_rank_points( - data) + our_points = ba.app.accounts_v1.get_league_rank_points(data) progress = float(our_points) / data['scores'][-1][1] self._percent = int(progress * 100.0) self._rank = None @@ -259,9 +281,11 @@ class LeagueRankButton: # If this is the first set, league has changed, or percent # has decreased, snap the smooth value immediately. - if (self._smooth_percent is None - or prev_league != self._league - or self._percent < int(self._smooth_percent)): + if ( + self._smooth_percent is None + or prev_league != self._league + or self._percent < int(self._smooth_percent) + ): self._smooth_percent = float(self._percent) status_text = str(int(self._smooth_percent)) + '%' @@ -271,10 +295,14 @@ class LeagueRankButton: status_text = '-' # If we're doing a smooth update, set a timer. - if (self._rank is not None and self._smooth_rank is not None - and int(self._smooth_rank) != self._rank): - self._improvement_text = str(-(int(self._rank) - - int(self._smooth_rank))) + if ( + self._rank is not None + and self._smooth_rank is not None + and int(self._smooth_rank) != self._rank + ): + self._improvement_text = str( + -(int(self._rank) - int(self._smooth_rank)) + ) diff = abs(self._rank - self._smooth_rank) if diff > 100: self._smooth_increase_speed = diff / 80.0 @@ -285,25 +313,34 @@ class LeagueRankButton: else: self._smooth_increase_speed = diff / 40.0 self._smooth_increase_speed = max(0.4, self._smooth_increase_speed) - ba.timer(self._smooth_update_delay, - ba.WeakCall(self._start_smooth_update), - timetype=ba.TimeType.REAL, - timeformat=ba.TimeFormat.MILLISECONDS) + ba.timer( + self._smooth_update_delay, + ba.WeakCall(self._start_smooth_update), + timetype=ba.TimeType.REAL, + timeformat=ba.TimeFormat.MILLISECONDS, + ) - if (self._percent is not None and self._smooth_percent is not None - and int(self._smooth_percent) != self._percent): + if ( + self._percent is not None + and self._smooth_percent is not None + and int(self._smooth_percent) != self._percent + ): self._improvement_text = str( - (int(self._percent) - int(self._smooth_percent))) + (int(self._percent) - int(self._smooth_percent)) + ) self._smooth_increase_speed = 0.3 - ba.timer(self._smooth_update_delay, - ba.WeakCall(self._start_smooth_update), - timetype=ba.TimeType.REAL, - timeformat=ba.TimeFormat.MILLISECONDS) + ba.timer( + self._smooth_update_delay, + ba.WeakCall(self._start_smooth_update), + timetype=ba.TimeType.REAL, + timeformat=ba.TimeFormat.MILLISECONDS, + ) if do_percent: ba.textwidget( edit=self._title_text, - text=ba.Lstr(resource='coopSelectWindow.toRankedText')) + text=ba.Lstr(resource='coopSelectWindow.toRankedText'), + ) else: try: assert data is not None @@ -320,13 +357,15 @@ class LeagueRankButton: except Exception: txt = ba.Lstr( resource='league.leagueRankText', - fallback_resource='coopSelectWindow.powerRankingText') + fallback_resource='coopSelectWindow.powerRankingText', + ) t_color = ba.app.ui.title_color ba.textwidget(edit=self._title_text, text=txt, color=t_color) ba.textwidget(edit=self._value_text, text=status_text) - def _on_power_ranking_query_response(self, - data: dict[str, Any] | None) -> None: + def _on_power_ranking_query_response( + self, data: dict[str, Any] | None + ) -> None: self._doing_power_ranking_query = False ba.app.accounts_v1.cache_league_rank_data(data) self._update_for_league_rank_data(data) @@ -346,16 +385,19 @@ class LeagueRankButton: # Send off a new power-ranking query if its been # long enough or whatnot. if not self._doing_power_ranking_query and ( - self._last_power_ranking_query_time is None - or cur_time - self._last_power_ranking_query_time > 30.0): + self._last_power_ranking_query_time is None + or cur_time - self._last_power_ranking_query_time > 30.0 + ): self._last_power_ranking_query_time = cur_time self._doing_power_ranking_query = True ba.internal.power_ranking_query( - callback=ba.WeakCall(self._on_power_ranking_query_response)) + callback=ba.WeakCall(self._on_power_ranking_query_response) + ) def _default_on_activate_call(self) -> None: # pylint: disable=cyclic-import from bastd.ui.league.rankwindow import LeagueRankWindow + LeagueRankWindow(modal=True, origin_widget=self._button) def set_position(self, position: tuple[float, float]) -> None: @@ -366,12 +408,18 @@ class LeagueRankButton: ba.buttonwidget(edit=self._button, position=self._position) ba.textwidget( edit=self._title_text, - position=(self._position[0] + self._size[0] * 0.5 * self._scale, - self._position[1] + self._size[1] * 0.82 * self._scale)) + position=( + self._position[0] + self._size[0] * 0.5 * self._scale, + self._position[1] + self._size[1] * 0.82 * self._scale, + ), + ) ba.textwidget( edit=self._value_text, - position=(self._position[0] + self._size[0] * 0.5 * self._scale, - self._position[1] + self._size[1] * 0.36 * self._scale)) + position=( + self._position[0] + self._size[0] * 0.5 * self._scale, + self._position[1] + self._size[1] * 0.36 * self._scale, + ), + ) def get_button(self) -> ba.Widget: """Return the underlying button ba.Widget>""" diff --git a/dist/ba_data/python/bastd/ui/league/rankwindow.py b/dist/ba_data/python/bastd/ui/league/rankwindow.py index 6c6dab4..3bf2589 100644 --- a/dist/ba_data/python/bastd/ui/league/rankwindow.py +++ b/dist/ba_data/python/bastd/ui/league/rankwindow.py @@ -1,6 +1,7 @@ # Released under the MIT License. See LICENSE for details. # """UI related to league rank.""" +# pylint: disable=too-many-lines from __future__ import annotations @@ -18,10 +19,12 @@ if TYPE_CHECKING: class LeagueRankWindow(ba.Window): """Window for showing league rank.""" - def __init__(self, - transition: str = 'in_right', - modal: bool = False, - origin_widget: ba.Widget | None = None): + def __init__( + self, + transition: str = 'in_right', + modal: bool = False, + origin_widget: ba.Widget | None = None, + ): ba.set_analytics_screen('League Rank Window') self._league_rank_data: dict[str, Any] | None = None @@ -40,8 +43,13 @@ class LeagueRankWindow(ba.Window): uiscale = ba.app.ui.uiscale self._width = 1320 if uiscale is ba.UIScale.SMALL else 1120 x_inset = 100 if uiscale is ba.UIScale.SMALL else 0 - self._height = (657 if uiscale is ba.UIScale.SMALL else - 710 if uiscale is ba.UIScale.MEDIUM else 800) + self._height = ( + 657 + if uiscale is ba.UIScale.SMALL + else 710 + if uiscale is ba.UIScale.MEDIUM + else 800 + ) self._r = 'coopSelectWindow' self._rdict = ba.app.lang.get_resource(self._r) top_extra = 20 if uiscale is ba.UIScale.SMALL else 0 @@ -51,25 +59,39 @@ class LeagueRankWindow(ba.Window): self._is_current_season = False self._can_do_more_button = True - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height + top_extra), - stack_offset=(0, -15) if uiscale is ba.UIScale.SMALL else ( - 0, 10) if uiscale is ba.UIScale.MEDIUM else (0, 0), - transition=transition, - scale_origin_stack_offset=scale_origin, - scale=(1.2 if uiscale is ba.UIScale.SMALL else - 0.93 if uiscale is ba.UIScale.MEDIUM else 0.8))) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height + top_extra), + stack_offset=(0, -15) + if uiscale is ba.UIScale.SMALL + else (0, 10) + if uiscale is ba.UIScale.MEDIUM + else (0, 0), + transition=transition, + scale_origin_stack_offset=scale_origin, + scale=( + 1.2 + if uiscale is ba.UIScale.SMALL + else 0.93 + if uiscale is ba.UIScale.MEDIUM + else 0.8 + ), + ) + ) self._back_button = btn = ba.buttonwidget( parent=self._root_widget, - position=(75 + x_inset, self._height - 87 - - (4 if uiscale is ba.UIScale.SMALL else 0)), + position=( + 75 + x_inset, + self._height - 87 - (4 if uiscale is ba.UIScale.SMALL else 0), + ), size=(120, 60), scale=1.2, autoselect=True, label=ba.Lstr(resource='doneText' if self._modal else 'backText'), button_type=None if self._modal else 'back', - on_activate_call=self._back) + on_activate_call=self._back, + ) self._title_text = ba.textwidget( parent=self._root_widget, @@ -77,33 +99,42 @@ class LeagueRankWindow(ba.Window): size=(0, 0), text=ba.Lstr( resource='league.leagueRankText', - fallback_resource='coopSelectWindow.powerRankingText'), + fallback_resource='coopSelectWindow.powerRankingText', + ), h_align='center', color=ba.app.ui.title_color, scale=1.4, maxwidth=600, - v_align='center') + v_align='center', + ) - ba.buttonwidget(edit=btn, - button_type='backSmall', - position=(75 + x_inset, self._height - 87 - - (2 if uiscale is ba.UIScale.SMALL else 0)), - size=(60, 55), - label=ba.charstr(ba.SpecialChar.BACK)) + ba.buttonwidget( + edit=btn, + button_type='backSmall', + position=( + 75 + x_inset, + self._height - 87 - (2 if uiscale is ba.UIScale.SMALL else 0), + ), + size=(60, 55), + label=ba.charstr(ba.SpecialChar.BACK), + ) self._scroll_width = self._width - (130 + 2 * x_inset) self._scroll_height = self._height - 160 - self._scrollwidget = ba.scrollwidget(parent=self._root_widget, - highlight=False, - position=(65 + x_inset, 70), - size=(self._scroll_width, - self._scroll_height), - center_small_content=True) + self._scrollwidget = ba.scrollwidget( + parent=self._root_widget, + highlight=False, + position=(65 + x_inset, 70), + size=(self._scroll_width, self._scroll_height), + center_small_content=True, + ) ba.widget(edit=self._scrollwidget, autoselect=True) ba.containerwidget(edit=self._scrollwidget, claims_left_right=True) - ba.containerwidget(edit=self._root_widget, - cancel_button=self._back_button, - selected_child=self._back_button) + ba.containerwidget( + edit=self._root_widget, + cancel_button=self._back_button, + selected_child=self._back_button, + ) self._last_power_ranking_query_time: float | None = None self._doing_power_ranking_query = False @@ -128,71 +159,107 @@ class LeagueRankWindow(ba.Window): if info is not None: self._update_for_league_rank_data(info) - self._update_timer = ba.Timer(1.0, - ba.WeakCall(self._update), - timetype=ba.TimeType.REAL, - repeat=True) + self._update_timer = ba.Timer( + 1.0, + ba.WeakCall(self._update), + timetype=ba.TimeType.REAL, + repeat=True, + ) self._update(show=(info is None)) def _on_achievements_press(self) -> None: from bastd.ui import achievements + # only allow this for all-time or the current season # (we currently don't keep specific achievement data for old seasons) if self._season == 'a' or self._is_current_season: + prab = self._power_ranking_achievements_button achievements.AchievementsWindow( - position=(self._power_ranking_achievements_button. - get_screen_space_center())) + position=prab.get_screen_space_center() + ) else: - ba.screenmessage(ba.Lstr( - resource='achievementsUnavailableForOldSeasonsText', - fallback_resource='unavailableText'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr( + resource='achievementsUnavailableForOldSeasonsText', + fallback_resource='unavailableText', + ), + color=(1, 0, 0), + ) ba.playsound(ba.getsound('error')) def _on_activity_mult_press(self) -> None: from bastd.ui import confirm + txt = ba.Lstr( resource='coopSelectWindow.activenessAllTimeInfoText' - if self._season == 'a' else 'coopSelectWindow.activenessInfoText', - subs=[('${MAX}', - str( - ba.internal.get_v1_account_misc_read_val( - 'activenessMax', 1.0)))]) - confirm.ConfirmWindow(txt, - cancel_button=False, - width=460, - height=150, - origin_widget=self._activity_mult_button) + if self._season == 'a' + else 'coopSelectWindow.activenessInfoText', + subs=[ + ( + '${MAX}', + str( + ba.internal.get_v1_account_misc_read_val( + 'activenessMax', 1.0 + ) + ), + ) + ], + ) + confirm.ConfirmWindow( + txt, + cancel_button=False, + width=460, + height=150, + origin_widget=self._activity_mult_button, + ) def _on_pro_mult_press(self) -> None: from bastd.ui import confirm - txt = ba.Lstr(resource='coopSelectWindow.proMultInfoText', - subs=[('${PERCENT}', - str( - ba.internal.get_v1_account_misc_read_val( - 'proPowerRankingBoost', 10))), - ('${PRO}', - ba.Lstr(resource='store.bombSquadProNameText', - subs=[('${APP_NAME}', - ba.Lstr(resource='titleText'))]))]) - confirm.ConfirmWindow(txt, - cancel_button=False, - width=460, - height=130, - origin_widget=self._pro_mult_button) + + txt = ba.Lstr( + resource='coopSelectWindow.proMultInfoText', + subs=[ + ( + '${PERCENT}', + str( + ba.internal.get_v1_account_misc_read_val( + 'proPowerRankingBoost', 10 + ) + ), + ), + ( + '${PRO}', + ba.Lstr( + resource='store.bombSquadProNameText', + subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))], + ), + ), + ], + ) + confirm.ConfirmWindow( + txt, + cancel_button=False, + width=460, + height=130, + origin_widget=self._pro_mult_button, + ) def _on_trophies_press(self) -> None: from bastd.ui.trophies import TrophiesWindow + info = self._league_rank_data if info is not None: - TrophiesWindow(position=self._power_ranking_trophies_button. - get_screen_space_center(), - data=info) + prtb = self._power_ranking_trophies_button + TrophiesWindow( + position=prtb.get_screen_space_center(), + data=info, + ) else: ba.playsound(ba.getsound('error')) - def _on_power_ranking_query_response(self, - data: dict[str, Any] | None) -> None: + def _on_power_ranking_query_response( + self, data: dict[str, Any] | None + ) -> None: self._doing_power_ranking_query = False # important: *only* cache this if we requested the current season.. if data is not None and data.get('s', None) is None: @@ -222,8 +289,9 @@ class LeagueRankWindow(ba.Window): # send off a new power-ranking query if its been long enough or our # requested season has changed or whatnot.. if not self._doing_power_ranking_query and ( - self._last_power_ranking_query_time is None - or cur_time - self._last_power_ranking_query_time > 30.0): + self._last_power_ranking_query_time is None + or cur_time - self._last_power_ranking_query_time > 30.0 + ): try: if show: ba.textwidget(edit=self._league_title_text, text='') @@ -231,9 +299,11 @@ class LeagueRankWindow(ba.Window): ba.textwidget(edit=self._league_number_text, text='') ba.textwidget( edit=self._your_power_ranking_text, - text=ba.Lstr(value='${A}...', - subs=[('${A}', - ba.Lstr(resource='loadingText'))])) + text=ba.Lstr( + value='${A}...', + subs=[('${A}', ba.Lstr(resource='loadingText'))], + ), + ) ba.textwidget(edit=self._to_ranked_text, text='') ba.textwidget(edit=self._power_ranking_rank_text, text='') ba.textwidget(edit=self._season_ends_text, text='') @@ -245,7 +315,8 @@ class LeagueRankWindow(ba.Window): self._doing_power_ranking_query = True ba.internal.power_ranking_query( season=self._requested_season, - callback=ba.WeakCall(self._on_power_ranking_query_response)) + callback=ba.WeakCall(self._on_power_ranking_query_response), + ) def _refresh(self) -> None: # pylint: disable=too-many-statements @@ -256,7 +327,8 @@ class LeagueRankWindow(ba.Window): self._subcontainer = ba.containerwidget( parent=self._scrollwidget, size=(self._subcontainerwidth, self._subcontainerheight), - background=False) + background=False, + ) w_parent = self._subcontainer v = self._subcontainerheight - 20 @@ -273,17 +345,19 @@ class LeagueRankWindow(ba.Window): tally_maxwidth = 120 v2 -= 70 - ba.textwidget(parent=w_parent, - position=(h2 - 60, v2 + 106), - size=(0, 0), - flatness=1.0, - shadow=0.0, - text=ba.Lstr(resource='coopSelectWindow.pointsText'), - h_align='left', - v_align='center', - scale=0.8, - color=(1, 1, 1, 0.3), - maxwidth=200) + ba.textwidget( + parent=w_parent, + position=(h2 - 60, v2 + 106), + size=(0, 0), + flatness=1.0, + shadow=0.0, + text=ba.Lstr(resource='coopSelectWindow.pointsText'), + h_align='left', + v_align='center', + scale=0.8, + color=(1, 1, 1, 0.3), + maxwidth=200, + ) self._power_ranking_achievements_button = ba.buttonwidget( parent=w_parent, @@ -296,7 +370,8 @@ class LeagueRankWindow(ba.Window): left_widget=self._back_button, color=(0.5, 0.5, 0.6), textcolor=(0.7, 0.7, 0.8), - label='') + label='', + ) self._power_ranking_achievement_total_text = ba.textwidget( parent=w_parent, @@ -309,7 +384,8 @@ class LeagueRankWindow(ba.Window): v_align='center', scale=0.8, color=tally_color, - maxwidth=tally_maxwidth) + maxwidth=tally_maxwidth, + ) v2 -= 80 @@ -323,7 +399,8 @@ class LeagueRankWindow(ba.Window): left_widget=self._back_button, color=(0.5, 0.5, 0.6), textcolor=(0.7, 0.7, 0.8), - label='') + label='', + ) self._power_ranking_trophies_total_text = ba.textwidget( parent=w_parent, position=(h2 + h_offs_tally, v2 + 45), @@ -335,7 +412,8 @@ class LeagueRankWindow(ba.Window): v_align='center', scale=0.8, color=tally_color, - maxwidth=tally_maxwidth) + maxwidth=tally_maxwidth, + ) v2 -= 100 @@ -350,7 +428,8 @@ class LeagueRankWindow(ba.Window): v_align='center', scale=0.8, color=(1, 1, 1, 0.3), - maxwidth=200) + maxwidth=200, + ) self._activity_mult_button: ba.Widget | None if ba.internal.get_v1_account_misc_read_val('act', False): @@ -365,7 +444,8 @@ class LeagueRankWindow(ba.Window): on_activate_call=ba.WeakCall(self._on_activity_mult_press), left_widget=self._back_button, color=(0.5, 0.5, 0.6), - textcolor=(0.7, 0.7, 0.8)) + textcolor=(0.7, 0.7, 0.8), + ) self._activity_mult_text = ba.textwidget( parent=w_parent, @@ -378,7 +458,8 @@ class LeagueRankWindow(ba.Window): v_align='center', scale=0.8, color=tally_color, - maxwidth=tally_maxwidth) + maxwidth=tally_maxwidth, + ) v2 -= 65 else: self._activity_mult_button = None @@ -389,41 +470,46 @@ class LeagueRankWindow(ba.Window): size=(200, 60), icon=ba.gettexture('logo'), icon_color=(0.3, 0, 0.3), - label=ba.Lstr(resource='store.bombSquadProNameText', - subs=[('${APP_NAME}', ba.Lstr(resource='titleText')) - ]), + label=ba.Lstr( + resource='store.bombSquadProNameText', + subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))], + ), autoselect=True, on_activate_call=ba.WeakCall(self._on_pro_mult_press), left_widget=self._back_button, color=(0.5, 0.5, 0.6), - textcolor=(0.7, 0.7, 0.8)) + textcolor=(0.7, 0.7, 0.8), + ) - self._pro_mult_text = ba.textwidget(parent=w_parent, - position=(h2 + h_offs_tally, - v2 + 40), - size=(0, 0), - flatness=1.0, - shadow=0.0, - text='-', - h_align='left', - v_align='center', - scale=0.8, - color=tally_color, - maxwidth=tally_maxwidth) + self._pro_mult_text = ba.textwidget( + parent=w_parent, + position=(h2 + h_offs_tally, v2 + 40), + size=(0, 0), + flatness=1.0, + shadow=0.0, + text='-', + h_align='left', + v_align='center', + scale=0.8, + color=tally_color, + maxwidth=tally_maxwidth, + ) v2 -= 30 v2 -= spc - ba.textwidget(parent=w_parent, - position=(h2 + h_offs_tally - 10 - 40, v2 + 35), - size=(0, 0), - flatness=1.0, - shadow=0.0, - text=ba.Lstr(resource='finalScoreText'), - h_align='right', - v_align='center', - scale=0.9, - color=worth_color, - maxwidth=150) + ba.textwidget( + parent=w_parent, + position=(h2 + h_offs_tally - 10 - 40, v2 + 35), + size=(0, 0), + flatness=1.0, + shadow=0.0, + text=ba.Lstr(resource='finalScoreText'), + h_align='right', + v_align='center', + scale=0.9, + color=worth_color, + maxwidth=150, + ) self._power_ranking_total_text = ba.textwidget( parent=w_parent, position=(h2 + h_offs_tally - 40, v2 + 35), @@ -435,7 +521,8 @@ class LeagueRankWindow(ba.Window): v_align='center', scale=0.9, color=tally_color, - maxwidth=tally_maxwidth) + maxwidth=tally_maxwidth, + ) self._season_show_text = ba.textwidget( parent=w_parent, @@ -448,92 +535,105 @@ class LeagueRankWindow(ba.Window): v_align='center', scale=0.8, shadow=0, - flatness=1.0) + flatness=1.0, + ) - self._league_title_text = ba.textwidget(parent=w_parent, - position=(470, v - 97), - size=(0, 0), - color=(0.6, 0.6, 0.7), - maxwidth=230, - text='', - h_align='center', - v_align='center', - scale=0.9, - shadow=0, - flatness=1.0) + self._league_title_text = ba.textwidget( + parent=w_parent, + position=(470, v - 97), + size=(0, 0), + color=(0.6, 0.6, 0.7), + maxwidth=230, + text='', + h_align='center', + v_align='center', + scale=0.9, + shadow=0, + flatness=1.0, + ) self._league_text_scale = 1.8 self._league_text_maxwidth = 210 - self._league_text = ba.textwidget(parent=w_parent, - position=(470, v - 140), - size=(0, 0), - color=(1, 1, 1), - maxwidth=self._league_text_maxwidth, - text='-', - h_align='center', - v_align='center', - scale=self._league_text_scale, - shadow=1.0, - flatness=1.0) + self._league_text = ba.textwidget( + parent=w_parent, + position=(470, v - 140), + size=(0, 0), + color=(1, 1, 1), + maxwidth=self._league_text_maxwidth, + text='-', + h_align='center', + v_align='center', + scale=self._league_text_scale, + shadow=1.0, + flatness=1.0, + ) self._league_number_base_pos = (470, v - 140) - self._league_number_text = ba.textwidget(parent=w_parent, - position=(470, v - 140), - size=(0, 0), - color=(1, 1, 1), - maxwidth=100, - text='', - h_align='left', - v_align='center', - scale=0.8, - shadow=1.0, - flatness=1.0) + self._league_number_text = ba.textwidget( + parent=w_parent, + position=(470, v - 140), + size=(0, 0), + color=(1, 1, 1), + maxwidth=100, + text='', + h_align='left', + v_align='center', + scale=0.8, + shadow=1.0, + flatness=1.0, + ) - self._your_power_ranking_text = ba.textwidget(parent=w_parent, - position=(470, - v - 142 - 70), - size=(0, 0), - color=(0.6, 0.6, 0.7), - maxwidth=230, - text='', - h_align='center', - v_align='center', - scale=0.9, - shadow=0, - flatness=1.0) + self._your_power_ranking_text = ba.textwidget( + parent=w_parent, + position=(470, v - 142 - 70), + size=(0, 0), + color=(0.6, 0.6, 0.7), + maxwidth=230, + text='', + h_align='center', + v_align='center', + scale=0.9, + shadow=0, + flatness=1.0, + ) - self._to_ranked_text = ba.textwidget(parent=w_parent, - position=(470, v - 250 - 70), - size=(0, 0), - color=(0.6, 0.6, 0.7), - maxwidth=230, - text='', - h_align='center', - v_align='center', - scale=0.8, - shadow=0, - flatness=1.0) + self._to_ranked_text = ba.textwidget( + parent=w_parent, + position=(470, v - 250 - 70), + size=(0, 0), + color=(0.6, 0.6, 0.7), + maxwidth=230, + text='', + h_align='center', + v_align='center', + scale=0.8, + shadow=0, + flatness=1.0, + ) - self._power_ranking_rank_text = ba.textwidget(parent=w_parent, - position=(473, - v - 210 - 70), - size=(0, 0), - big=False, - text='-', - h_align='center', - v_align='center', - scale=1.0) + self._power_ranking_rank_text = ba.textwidget( + parent=w_parent, + position=(473, v - 210 - 70), + size=(0, 0), + big=False, + text='-', + h_align='center', + v_align='center', + scale=1.0, + ) - self._season_ends_text = ba.textwidget(parent=w_parent, - position=(470, v - 380), - size=(0, 0), - color=(0.6, 0.6, 0.6), - maxwidth=230, - text='', - h_align='center', - v_align='center', - scale=0.9, - shadow=0, - flatness=1.0) + self._season_ends_text = ba.textwidget( + parent=w_parent, + position=(470, v - 380), + size=(0, 0), + color=(0.6, 0.6, 0.6), + maxwidth=230, + text='', + h_align='center', + v_align='center', + scale=0.9, + shadow=0, + flatness=1.0, + ) self._trophy_counts_reset_text = ba.textwidget( parent=w_parent, position=(470, v - 410), @@ -545,7 +645,8 @@ class LeagueRankWindow(ba.Window): v_align='center', scale=0.8, shadow=0, - flatness=1.0) + flatness=1.0, + ) self._power_ranking_score_widgets = [] @@ -554,15 +655,16 @@ class LeagueRankWindow(ba.Window): h = 707 v -= 451 - self._see_more_button = ba.buttonwidget(parent=w_parent, - label=self._rdict.seeMoreText, - position=(h, v), - color=(0.5, 0.5, 0.6), - textcolor=(0.7, 0.7, 0.8), - size=(230, 60), - autoselect=True, - on_activate_call=ba.WeakCall( - self._on_more_press)) + self._see_more_button = ba.buttonwidget( + parent=w_parent, + label=self._rdict.seeMoreText, + position=(h, v), + color=(0.5, 0.5, 0.6), + textcolor=(0.7, 0.7, 0.8), + size=(230, 60), + autoselect=True, + on_activate_call=ba.WeakCall(self._on_more_press), + ) def _on_more_press(self) -> None: our_login_id = ba.internal.get_public_login_id() @@ -570,32 +672,37 @@ class LeagueRankWindow(ba.Window): # 'resolvedAccountID', None) if not self._can_do_more_button or our_login_id is None: ba.playsound(ba.getsound('error')) - ba.screenmessage(ba.Lstr(resource='unavailableText'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr(resource='unavailableText'), color=(1, 0, 0) + ) return if self._season is None: season_str = '' else: - season_str = ( - '&season=' + - ('all_time' if self._season == 'a' else self._season)) + season_str = '&season=' + ( + 'all_time' if self._season == 'a' else self._season + ) if self._league_url_arg != '': league_str = '&league=' + self._league_url_arg else: league_str = '' - ba.open_url(ba.internal.get_master_server_address() + - '/highscores?list=powerRankings&v=2' + league_str + - season_str + '&player=' + our_login_id) + ba.open_url( + ba.internal.get_master_server_address() + + '/highscores?list=powerRankings&v=2' + + league_str + + season_str + + '&player=' + + our_login_id + ) - def _update_for_league_rank_data(self, - data: dict[str, Any] | None) -> None: + def _update_for_league_rank_data(self, data: dict[str, Any] | None) -> None: # pylint: disable=too-many-statements # pylint: disable=too-many-branches # pylint: disable=too-many-locals if not self._root_widget: return accounts = ba.app.accounts_v1 - in_top = (data is not None and data['rank'] is not None) + in_top = data is not None and data['rank'] is not None eq_text = self._rdict.powerRankingPointsEqualsText pts_txt = self._rdict.powerRankingPointsText num_text = ba.Lstr(resource='numberText').evaluate() @@ -604,8 +711,9 @@ class LeagueRankWindow(ba.Window): self._can_do_more_button = True extra_text = '' if ba.internal.get_v1_account_state() != 'signed_in': - status_text = '(' + ba.Lstr( - resource='notSignedInText').evaluate() + ')' + status_text = ( + '(' + ba.Lstr(resource='notSignedInText').evaluate() + ')' + ) elif in_top: assert data is not None status_text = num_text.replace('${NUMBER}', str(data['rank'])) @@ -615,25 +723,27 @@ class LeagueRankWindow(ba.Window): # at the end.. if not data['scores']: status_text = ( - self._rdict.powerRankingFinishedSeasonUnrankedText) + self._rdict.powerRankingFinishedSeasonUnrankedText + ) extra_text = '' finished_season_unranked = True self._can_do_more_button = False else: our_points = accounts.get_league_rank_points(data) - progress = float(our_points) / max(1, - data['scores'][-1][1]) + progress = float(our_points) / max(1, data['scores'][-1][1]) status_text = str(int(progress * 100.0)) + '%' extra_text = ( - '\n' + - self._rdict.powerRankingPointsToRankedText.replace( - '${CURRENT}', str(our_points)).replace( - '${REMAINING}', str(data['scores'][-1][1]))) + '\n' + + self._rdict.powerRankingPointsToRankedText.replace( + '${CURRENT}', str(our_points) + ).replace('${REMAINING}', str(data['scores'][-1][1])) + ) do_percent = True except Exception: ba.print_exception('Error updating power ranking.') status_text = self._rdict.powerRankingNotInTopText.replace( - '${NUMBER}', str(data['listSize'])) + '${NUMBER}', str(data['listSize']) + ) extra_text = '' else: status_text = '-' @@ -658,8 +768,11 @@ class LeagueRankWindow(ba.Window): season_choices.append(ssn) if ssn != 'a' and not did_first: season_choices_display.append( - ba.Lstr(resource='league.currentSeasonText', - subs=[('${NUMBER}', ssn)])) + ba.Lstr( + resource='league.currentSeasonText', + subs=[('${NUMBER}', ssn)], + ) + ) did_first = True # if we either did not specify a season or specified the # first, we're looking at the current.. @@ -667,11 +780,15 @@ class LeagueRankWindow(ba.Window): self._is_current_season = True elif ssn == 'a': season_choices_display.append( - ba.Lstr(resource='league.allTimeText')) + ba.Lstr(resource='league.allTimeText') + ) else: season_choices_display.append( - ba.Lstr(resource='league.seasonText', - subs=[('${NUMBER}', ssn)])) + ba.Lstr( + resource='league.seasonText', + subs=[('${NUMBER}', ssn)], + ) + ) assert self._subcontainer self._season_popup_menu = popup_ui.PopupMenu( parent=self._subcontainer, @@ -681,21 +798,30 @@ class LeagueRankWindow(ba.Window): choices=season_choices, on_value_change_call=ba.WeakCall(self._on_season_change), choices_display=season_choices_display, - current_choice=self._season) + current_choice=self._season, + ) if popup_was_selected: ba.containerwidget( edit=self._subcontainer, - selected_child=self._season_popup_menu.get_button()) + selected_child=self._season_popup_menu.get_button(), + ) ba.widget(edit=self._see_more_button, show_buffer_bottom=100) - ba.widget(edit=self._season_popup_menu.get_button(), - up_widget=self._back_button) - ba.widget(edit=self._back_button, - down_widget=self._power_ranking_achievements_button, - right_widget=self._season_popup_menu.get_button()) + ba.widget( + edit=self._season_popup_menu.get_button(), + up_widget=self._back_button, + ) + ba.widget( + edit=self._back_button, + down_widget=self._power_ranking_achievements_button, + right_widget=self._season_popup_menu.get_button(), + ) - ba.textwidget(edit=self._league_title_text, - text='' if self._season == 'a' else ba.Lstr( - resource='league.leagueText')) + ba.textwidget( + edit=self._league_title_text, + text='' + if self._season == 'a' + else ba.Lstr(resource='league.leagueText'), + ) if data is None: lname = '' @@ -709,11 +835,13 @@ class LeagueRankWindow(ba.Window): self._league_url_arg = '' else: lnum = ('[' + str(data['l']['i']) + ']') if data['l']['i2'] else '' - lname = ba.Lstr(translate=('leagueNames', - data['l']['n'])).evaluate() + lname = ba.Lstr( + translate=('leagueNames', data['l']['n']) + ).evaluate() lcolor = data['l']['c'] - self._league_url_arg = (data['l']['n'] + '_' + - str(data['l']['i'])).lower() + self._league_url_arg = ( + data['l']['n'] + '_' + str(data['l']['i']) + ).lower() to_end_string: ba.Lstr | str if data is None or self._season == 'a' or data['se'] is None: @@ -724,87 +852,127 @@ class LeagueRankWindow(ba.Window): days_to_end = data['se'][0] minutes_to_end = data['se'][1] if days_to_end > 0: - to_end_string = ba.Lstr(resource='league.seasonEndsDaysText', - subs=[('${NUMBER}', str(days_to_end))]) + to_end_string = ba.Lstr( + resource='league.seasonEndsDaysText', + subs=[('${NUMBER}', str(days_to_end))], + ) elif days_to_end == 0 and minutes_to_end >= 60: - to_end_string = ba.Lstr(resource='league.seasonEndsHoursText', - subs=[('${NUMBER}', - str(minutes_to_end // 60))]) + to_end_string = ba.Lstr( + resource='league.seasonEndsHoursText', + subs=[('${NUMBER}', str(minutes_to_end // 60))], + ) elif days_to_end == 0 and minutes_to_end >= 0: to_end_string = ba.Lstr( resource='league.seasonEndsMinutesText', - subs=[('${NUMBER}', str(minutes_to_end))]) + subs=[('${NUMBER}', str(minutes_to_end))], + ) else: to_end_string = ba.Lstr( resource='league.seasonEndedDaysAgoText', - subs=[('${NUMBER}', str(-(days_to_end + 1)))]) + subs=[('${NUMBER}', str(-(days_to_end + 1)))], + ) ba.textwidget(edit=self._season_ends_text, text=to_end_string) - ba.textwidget(edit=self._trophy_counts_reset_text, - text=ba.Lstr(resource='league.trophyCountsResetText') - if self._is_current_season and show_season_end else '') + ba.textwidget( + edit=self._trophy_counts_reset_text, + text=ba.Lstr(resource='league.trophyCountsResetText') + if self._is_current_season and show_season_end + else '', + ) ba.textwidget(edit=self._league_text, text=lname, color=lcolor) l_text_width = min( self._league_text_maxwidth, - ba.internal.get_string_width(lname, suppress_warning=True) * - self._league_text_scale) + ba.internal.get_string_width(lname, suppress_warning=True) + * self._league_text_scale, + ) ba.textwidget( edit=self._league_number_text, text=lnum, color=lcolor, - position=(self._league_number_base_pos[0] + l_text_width * 0.5 + 8, - self._league_number_base_pos[1] + 10)) + position=( + self._league_number_base_pos[0] + l_text_width * 0.5 + 8, + self._league_number_base_pos[1] + 10, + ), + ) ba.textwidget( edit=self._to_ranked_text, - text=ba.Lstr(resource='coopSelectWindow.toRankedText').evaluate() + - '' + extra_text if do_percent else '') + text=ba.Lstr(resource='coopSelectWindow.toRankedText').evaluate() + + '' + + extra_text + if do_percent + else '', + ) ba.textwidget( edit=self._your_power_ranking_text, text=ba.Lstr( resource='rankText', - fallback_resource='coopSelectWindow.yourPowerRankingText') if - (not do_percent) else '') + fallback_resource='coopSelectWindow.yourPowerRankingText', + ) + if (not do_percent) + else '', + ) - ba.textwidget(edit=self._power_ranking_rank_text, - position=(473, v - 70 - (170 if do_percent else 220)), - text=status_text, - big=(in_top or do_percent), - scale=3.0 if (in_top or do_percent) else - 0.7 if finished_season_unranked else 1.0) + ba.textwidget( + edit=self._power_ranking_rank_text, + position=(473, v - 70 - (170 if do_percent else 220)), + text=status_text, + big=(in_top or do_percent), + scale=3.0 + if (in_top or do_percent) + else 0.7 + if finished_season_unranked + else 1.0, + ) if self._activity_mult_button is not None: if data is None or data['act'] is None: - ba.buttonwidget(edit=self._activity_mult_button, - textcolor=(0.7, 0.7, 0.8, 0.5), - icon_color=(0.5, 0, 0.5, 0.3)) + ba.buttonwidget( + edit=self._activity_mult_button, + textcolor=(0.7, 0.7, 0.8, 0.5), + icon_color=(0.5, 0, 0.5, 0.3), + ) ba.textwidget(edit=self._activity_mult_text, text=' -') else: - ba.buttonwidget(edit=self._activity_mult_button, - textcolor=(0.7, 0.7, 0.8, 1.0), - icon_color=(0.5, 0, 0.5, 1.0)) + ba.buttonwidget( + edit=self._activity_mult_button, + textcolor=(0.7, 0.7, 0.8, 1.0), + icon_color=(0.5, 0, 0.5, 1.0), + ) # pylint: disable=consider-using-f-string - ba.textwidget(edit=self._activity_mult_text, - text='x ' + ('%.2f' % data['act'])) + ba.textwidget( + edit=self._activity_mult_text, + text='x ' + ('%.2f' % data['act']), + ) have_pro = False if data is None else data['p'] - pro_mult = 1.0 + float( - ba.internal.get_v1_account_misc_read_val('proPowerRankingBoost', - 0.0)) * 0.01 + pro_mult = ( + 1.0 + + float( + ba.internal.get_v1_account_misc_read_val( + 'proPowerRankingBoost', 0.0 + ) + ) + * 0.01 + ) # pylint: disable=consider-using-f-string - ba.textwidget(edit=self._pro_mult_text, - text=' -' if - (data is None or not have_pro) else 'x ' + - ('%.2f' % pro_mult)) - ba.buttonwidget(edit=self._pro_mult_button, - textcolor=(0.7, 0.7, 0.8, (1.0 if have_pro else 0.5)), - icon_color=(0.5, 0, 0.5) if have_pro else - (0.5, 0, 0.5, 0.2)) - ba.buttonwidget(edit=self._power_ranking_achievements_button, - label=('' if data is None else - (str(data['a']) + ' ')) + - ba.Lstr(resource='achievementsText').evaluate()) + ba.textwidget( + edit=self._pro_mult_text, + text=' -' + if (data is None or not have_pro) + else 'x ' + ('%.2f' % pro_mult), + ) + ba.buttonwidget( + edit=self._pro_mult_button, + textcolor=(0.7, 0.7, 0.8, (1.0 if have_pro else 0.5)), + icon_color=(0.5, 0, 0.5) if have_pro else (0.5, 0, 0.5, 0.2), + ) + ba.buttonwidget( + edit=self._power_ranking_achievements_button, + label=('' if data is None else (str(data['a']) + ' ')) + + ba.Lstr(resource='achievementsText').evaluate(), + ) # for the achievement value, use the number they gave us for # non-current seasons; otherwise calc our own @@ -816,28 +984,39 @@ class LeagueRankWindow(ba.Window): if data is not None and 'at' in data: total_ach_value = data['at'] - ba.textwidget(edit=self._power_ranking_achievement_total_text, - text='-' if data is None else - ('+ ' + - pts_txt.replace('${NUMBER}', str(total_ach_value)))) + ba.textwidget( + edit=self._power_ranking_achievement_total_text, + text='-' + if data is None + else ('+ ' + pts_txt.replace('${NUMBER}', str(total_ach_value))), + ) - total_trophies_count = (accounts.get_league_rank_points( - data, 'trophyCount')) - total_trophies_value = (accounts.get_league_rank_points( - data, 'trophies')) - ba.buttonwidget(edit=self._power_ranking_trophies_button, - label=('' if data is None else - (str(total_trophies_count) + ' ')) + - ba.Lstr(resource='trophiesText').evaluate()) + total_trophies_count = accounts.get_league_rank_points( + data, 'trophyCount' + ) + total_trophies_value = accounts.get_league_rank_points(data, 'trophies') + ba.buttonwidget( + edit=self._power_ranking_trophies_button, + label=('' if data is None else (str(total_trophies_count) + ' ')) + + ba.Lstr(resource='trophiesText').evaluate(), + ) ba.textwidget( edit=self._power_ranking_trophies_total_text, - text='-' if data is None else - ('+ ' + pts_txt.replace('${NUMBER}', str(total_trophies_value)))) + text='-' + if data is None + else ( + '+ ' + pts_txt.replace('${NUMBER}', str(total_trophies_value)) + ), + ) ba.textwidget( edit=self._power_ranking_total_text, - text='-' if data is None else eq_text.replace( - '${NUMBER}', str(accounts.get_league_rank_points(data)))) + text='-' + if data is None + else eq_text.replace( + '${NUMBER}', str(accounts.get_league_rank_points(data)) + ), + ) for widget in self._power_ranking_score_widgets: widget.delete() self._power_ranking_score_widgets = [] @@ -851,60 +1030,73 @@ class LeagueRankWindow(ba.Window): h2 = 680 is_us = score[3] self._power_ranking_score_widgets.append( - ba.textwidget(parent=w_parent, - position=(h2 - 20, v2), - size=(0, 0), - color=(1, 1, 1) if is_us else (0.6, 0.6, 0.7), - maxwidth=40, - flatness=1.0, - shadow=0.0, - text=num_text.replace('${NUMBER}', - str(score[0])), - h_align='right', - v_align='center', - scale=0.5)) + ba.textwidget( + parent=w_parent, + position=(h2 - 20, v2), + size=(0, 0), + color=(1, 1, 1) if is_us else (0.6, 0.6, 0.7), + maxwidth=40, + flatness=1.0, + shadow=0.0, + text=num_text.replace('${NUMBER}', str(score[0])), + h_align='right', + v_align='center', + scale=0.5, + ) + ) self._power_ranking_score_widgets.append( - ba.textwidget(parent=w_parent, - position=(h2 + 20, v2), - size=(0, 0), - color=(1, 1, 1) if is_us else tally_color, - maxwidth=60, - text=str(score[1]), - flatness=1.0, - shadow=0.0, - h_align='center', - v_align='center', - scale=0.7)) - txt = ba.textwidget(parent=w_parent, - position=(h2 + 60, v2 - (28 * 0.5) / 0.9), - size=(210 / 0.9, 28), - color=(1, 1, 1) if is_us else (0.6, 0.6, 0.6), - maxwidth=210, - flatness=1.0, - shadow=0.0, - autoselect=True, - selectable=True, - click_activate=True, - text=score[2], - h_align='left', - v_align='center', - scale=0.9) + ba.textwidget( + parent=w_parent, + position=(h2 + 20, v2), + size=(0, 0), + color=(1, 1, 1) if is_us else tally_color, + maxwidth=60, + text=str(score[1]), + flatness=1.0, + shadow=0.0, + h_align='center', + v_align='center', + scale=0.7, + ) + ) + txt = ba.textwidget( + parent=w_parent, + position=(h2 + 60, v2 - (28 * 0.5) / 0.9), + size=(210 / 0.9, 28), + color=(1, 1, 1) if is_us else (0.6, 0.6, 0.6), + maxwidth=210, + flatness=1.0, + shadow=0.0, + autoselect=True, + selectable=True, + click_activate=True, + text=score[2], + h_align='left', + v_align='center', + scale=0.9, + ) self._power_ranking_score_widgets.append(txt) - ba.textwidget(edit=txt, - on_activate_call=ba.Call(self._show_account_info, - score[4], txt)) + ba.textwidget( + edit=txt, + on_activate_call=ba.Call( + self._show_account_info, score[4], txt + ), + ) assert self._season_popup_menu is not None - ba.widget(edit=txt, - left_widget=self._season_popup_menu.get_button()) + ba.widget( + edit=txt, left_widget=self._season_popup_menu.get_button() + ) v2 -= 28 - def _show_account_info(self, account_id: str, - textwidget: ba.Widget) -> None: + def _show_account_info( + self, account_id: str, textwidget: ba.Widget + ) -> None: from bastd.ui.account import viewer + ba.playsound(ba.getsound('swish')) viewer.AccountViewerWindow( - account_id=account_id, - position=textwidget.get_screen_space_center()) + account_id=account_id, position=textwidget.get_screen_space_center() + ) def _on_season_change(self, value: str) -> None: self._requested_season = value @@ -916,9 +1108,12 @@ class LeagueRankWindow(ba.Window): def _back(self) -> None: from bastd.ui.coop.browser import CoopBrowserWindow + self._save_state() - ba.containerwidget(edit=self._root_widget, - transition=self._transition_out) + ba.containerwidget( + edit=self._root_widget, transition=self._transition_out + ) if not self._modal: ba.app.ui.set_main_menu_window( - CoopBrowserWindow(transition='in_left').get_root_widget()) + CoopBrowserWindow(transition='in_left').get_root_widget() + ) diff --git a/dist/ba_data/python/bastd/ui/mainmenu.py b/dist/ba_data/python/bastd/ui/mainmenu.py index 6e8ad23..a3d6773 100644 --- a/dist/ba_data/python/bastd/ui/mainmenu.py +++ b/dist/ba_data/python/bastd/ui/mainmenu.py @@ -21,8 +21,10 @@ class MainMenuWindow(ba.Window): # pylint: disable=cyclic-import import threading from bastd.mainmenu import MainMenuSession + self._in_game = not isinstance( - ba.internal.get_foreground_host_session(), MainMenuSession) + ba.internal.get_foreground_host_session(), MainMenuSession + ) # Preload some modules we use in a background thread so we won't # have a visual hitch when the user taps them. @@ -33,10 +35,14 @@ class MainMenuWindow(ba.Window): self._show_remote_app_info_on_first_launch() # Make a vanilla container; we'll modify it to our needs in refresh. - super().__init__(root_widget=ba.containerwidget( - transition=transition, - toolbar_visibility='menu_minimal_no_back' if self. - _in_game else 'menu_minimal_no_back')) + super().__init__( + root_widget=ba.containerwidget( + transition=transition, + toolbar_visibility='menu_minimal_no_back' + if self._in_game + else 'menu_minimal_no_back', + ) + ) # Grab this stuff in case it changes. self._is_demo = ba.app.demo_mode @@ -56,10 +62,11 @@ class MainMenuWindow(ba.Window): self._gather_button: ba.Widget | None = None self._start_button: ba.Widget | None = None self._watch_button: ba.Widget | None = None - self._gc_button: ba.Widget | None = None + self._account_button: ba.Widget | None = None self._how_to_play_button: ba.Widget | None = None self._credits_button: ba.Widget | None = None self._settings_button: ba.Widget | None = None + self._next_refresh_allow_time = 0.0 self._store_char_tex = self._get_store_char_tex() @@ -69,12 +76,17 @@ class MainMenuWindow(ba.Window): # Keep an eye on a few things and refresh if they change. self._account_state = ba.internal.get_v1_account_state() self._account_state_num = ba.internal.get_v1_account_state_num() - self._account_type = (ba.internal.get_v1_account_type() - if self._account_state == 'signed_in' else None) - self._refresh_timer = ba.Timer(1.0, - ba.WeakCall(self._check_refresh), - repeat=True, - timetype=ba.TimeType.REAL) + self._account_type = ( + ba.internal.get_v1_account_type() + if self._account_state == 'signed_in' + else None + ) + self._refresh_timer = ba.Timer( + 0.27, + ba.WeakCall(self._check_refresh), + repeat=True, + timetype=ba.TimeType.REAL, + ) # noinspection PyUnresolvedReferences @staticmethod @@ -102,52 +114,69 @@ class MainMenuWindow(ba.Window): app = ba.app force_test = False ba.internal.get_local_active_input_devices_count() - if (((app.on_tv or app.platform == 'mac') - and ba.app.config.get('launchCount', 0) <= 1) - or force_test): + if ( + (app.on_tv or app.platform == 'mac') + and ba.app.config.get('launchCount', 0) <= 1 + ) or force_test: def _check_show_bs_remote_window() -> None: try: from bastd.ui.getremote import GetBSRemoteWindow + ba.playsound(ba.getsound('swish')) GetBSRemoteWindow() except Exception: ba.print_exception( - 'Error showing get-remote window.') + 'Error showing get-remote window.' + ) - ba.timer(2.5, - _check_show_bs_remote_window, - timetype=ba.TimeType.REAL) + ba.timer( + 2.5, + _check_show_bs_remote_window, + timetype=ba.TimeType.REAL, + ) except Exception: ba.print_exception('Error showing get-remote-app info') def _get_store_char_tex(self) -> str: return ( - 'storeCharacterXmas' if ba.internal.get_v1_account_misc_read_val( - 'xmas', False) else - 'storeCharacterEaster' if ba.internal.get_v1_account_misc_read_val( - 'easter', False) else 'storeCharacter') + 'storeCharacterXmas' + if ba.internal.get_v1_account_misc_read_val('xmas', False) + else 'storeCharacterEaster' + if ba.internal.get_v1_account_misc_read_val('easter', False) + else 'storeCharacter' + ) def _check_refresh(self) -> None: if not self._root_widget: return + now = ba.time(ba.TimeType.REAL) + 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. - ba.app.main_menu_window_refresh_check_count += 1 - if ba.app.main_menu_window_refresh_check_count < 4: - return + # ba.app.main_menu_window_refresh_check_count += 1 + # if ba.app.main_menu_window_refresh_check_count < 4: + # return store_char_tex = self._get_store_char_tex() account_state_num = ba.internal.get_v1_account_state_num() - if (account_state_num != self._account_state_num - or store_char_tex != self._store_char_tex): + if ( + account_state_num != self._account_state_num + or store_char_tex != self._store_char_tex + ): self._store_char_tex = store_char_tex self._account_state_num = account_state_num - account_state = self._account_state = ( - ba.internal.get_v1_account_state()) - self._account_type = (ba.internal.get_v1_account_type() - if account_state == 'signed_in' else None) + account_state = ( + self._account_state + ) = ba.internal.get_v1_account_state() + self._account_type = ( + ba.internal.get_v1_account_type() + if account_state == 'signed_in' + else None + ) self._save_state() self._refresh() self._restore_state() @@ -177,21 +206,23 @@ class MainMenuWindow(ba.Window): self._r = 'mainMenu' app = ba.app - self._have_quit_button = (app.ui.uiscale is ba.UIScale.LARGE - or (app.platform == 'windows' - and app.subplatform == 'oculus')) + self._have_quit_button = app.ui.uiscale is ba.UIScale.LARGE or ( + app.platform == 'windows' and app.subplatform == 'oculus' + ) self._have_store_button = not self._in_game self._have_settings_button = ( - (not self._in_game or not app.toolbar_test) - and not (self._is_demo or self._is_arcade or self._is_iircade)) + not self._in_game or not app.toolbar_test + ) and not (self._is_demo or self._is_arcade or self._is_iircade) self._input_device = input_device = ba.internal.get_ui_input_device() self._input_player = input_device.player if input_device else None self._connected_to_remote_player = ( input_device.is_connected_to_remote_player() - if input_device else False) + if input_device + else False + ) positions: list[tuple[float, float, float]] = [] self._p_index = 0 @@ -212,20 +243,26 @@ class MainMenuWindow(ba.Window): autoselect=self._use_autoselect, label=ba.Lstr(resource=self._r + '.settingsText'), transition_delay=self._tdelay, - on_activate_call=self._settings) + on_activate_call=self._settings, + ) # Scattered eggs on easter. - if ba.internal.get_v1_account_misc_read_val( - 'easter', False) and not self._in_game: + if ( + ba.internal.get_v1_account_misc_read_val('easter', False) + and not self._in_game + ): icon_size = 34 - ba.imagewidget(parent=self._root_widget, - position=(h - icon_size * 0.5 - 15, - v + self._button_height * scale - - icon_size * 0.24 + 1.5), - transition_delay=self._tdelay, - size=(icon_size, icon_size), - texture=ba.gettexture('egg3'), - tilt_scale=0.0) + ba.imagewidget( + parent=self._root_widget, + position=( + h - icon_size * 0.5 - 15, + v + self._button_height * scale - icon_size * 0.24 + 1.5, + ), + transition_delay=self._tdelay, + size=(icon_size, icon_size), + texture=ba.gettexture('egg3'), + tilt_scale=0.0, + ) self._tdelay += self._t_delay_inc @@ -235,14 +272,15 @@ class MainMenuWindow(ba.Window): # If we're in a replay, we have a 'Leave Replay' button. if ba.internal.is_in_replay(): - ba.buttonwidget(parent=self._root_widget, - position=(h - self._button_width * 0.5 * scale, - v), - scale=scale, - size=(self._button_width, self._button_height), - autoselect=self._use_autoselect, - label=ba.Lstr(resource='replayEndText'), - on_activate_call=self._confirm_end_replay) + ba.buttonwidget( + parent=self._root_widget, + position=(h - self._button_width * 0.5 * scale, v), + scale=scale, + size=(self._button_width, self._button_height), + autoselect=self._use_autoselect, + label=ba.Lstr(resource='replayEndText'), + on_activate_call=self._confirm_end_replay, + ) elif ba.internal.get_foreground_host_session() is not None: ba.buttonwidget( parent=self._root_widget, @@ -250,8 +288,20 @@ class MainMenuWindow(ba.Window): scale=scale, size=(self._button_width, self._button_height), autoselect=self._use_autoselect, - label=ba.Lstr(resource=self._r + '.endGameText'), - on_activate_call=self._confirm_end_game) + label=ba.Lstr( + resource=self._r + + ( + '.endTestText' + if self._is_benchmark() + else '.endGameText' + ) + ), + on_activate_call=( + self._confirm_end_test + if self._is_benchmark() + else self._confirm_end_game + ), + ) # Assume we're in a client-session. else: ba.buttonwidget( @@ -261,7 +311,8 @@ class MainMenuWindow(ba.Window): size=(self._button_width, self._button_height), autoselect=self._use_autoselect, label=ba.Lstr(resource=self._r + '.leavePartyText'), - on_activate_call=self._confirm_leave_party) + on_activate_call=self._confirm_leave_party, + ) self._store_button: ba.Widget | None if self._have_store_button: @@ -276,20 +327,29 @@ class MainMenuWindow(ba.Window): scale=scale, on_activate_call=ba.WeakCall(self._on_store_pressed), sale_scale=1.3, - transition_delay=self._tdelay) + transition_delay=self._tdelay, + ) self._store_button = store_button = sbtn.get_button() uiscale = ba.app.ui.uiscale - icon_size = (55 if uiscale is ba.UIScale.SMALL else - 55 if uiscale is ba.UIScale.MEDIUM else 70) + icon_size = ( + 55 + if uiscale is ba.UIScale.SMALL + else 55 + if uiscale is ba.UIScale.MEDIUM + else 70 + ) ba.imagewidget( parent=self._root_widget, - position=(h - icon_size * 0.5, - v + self._button_height * scale - icon_size * 0.23), + position=( + h - icon_size * 0.5, + v + self._button_height * scale - icon_size * 0.23, + ), transition_delay=self._tdelay, size=(icon_size, icon_size), texture=ba.gettexture(self._store_char_tex), tilt_scale=0.0, - draw_controller=store_button) + draw_controller=store_button, + ) self._tdelay += self._t_delay_inc else: @@ -305,40 +365,57 @@ class MainMenuWindow(ba.Window): position=(h - self._button_width * 0.5 * scale, v), size=(self._button_width, self._button_height), scale=scale, - label=ba.Lstr(resource=self._r + - ('.quitText' if 'Mac' in - ba.app.user_agent_string else '.exitGameText')), + label=ba.Lstr( + resource=self._r + + ( + '.quitText' + if 'Mac' in ba.app.user_agent_string + else '.exitGameText' + ) + ), on_activate_call=self._quit, - transition_delay=self._tdelay) + transition_delay=self._tdelay, + ) # Scattered eggs on easter. if ba.internal.get_v1_account_misc_read_val('easter', False): icon_size = 30 - ba.imagewidget(parent=self._root_widget, - position=(h - icon_size * 0.5 + 25, - v + self._button_height * scale - - icon_size * 0.24 + 1.5), - transition_delay=self._tdelay, - size=(icon_size, icon_size), - texture=ba.gettexture('egg1'), - tilt_scale=0.0) + ba.imagewidget( + parent=self._root_widget, + position=( + h - icon_size * 0.5 + 25, + v + + self._button_height * scale + - icon_size * 0.24 + + 1.5, + ), + transition_delay=self._tdelay, + size=(icon_size, icon_size), + texture=ba.gettexture('egg1'), + tilt_scale=0.0, + ) - ba.containerwidget(edit=self._root_widget, - cancel_button=quit_button) + ba.containerwidget( + edit=self._root_widget, cancel_button=quit_button + ) self._tdelay += self._t_delay_inc 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 (not self._in_game and not self._have_quit_button - and ba.app.platform == 'android'): + if ( + not self._in_game + and not self._have_quit_button + and ba.app.platform == 'android' + ): def _do_quit() -> None: QuitWindow(swish=True, back=True) - ba.containerwidget(edit=self._root_widget, - on_cancel_call=_do_quit) + ba.containerwidget( + 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 @@ -359,13 +436,16 @@ class MainMenuWindow(ba.Window): v_offs = -100 self._replay_speed_text = ba.textwidget( parent=self._root_widget, - text=ba.Lstr(resource='watchWindow.playbackSpeedText', - subs=[('${SPEED}', str(1.23))]), + text=ba.Lstr( + resource='watchWindow.playbackSpeedText', + subs=[('${SPEED}', str(1.23))], + ), position=(h, v + v_offs + 7 * t_scale), h_align='center', v_align='center', size=(0, 0), - scale=t_scale) + scale=t_scale, + ) # Update to current value. self._change_replay_speed(0) @@ -375,26 +455,33 @@ class MainMenuWindow(ba.Window): 0.25, ba.WeakCall(self._change_replay_speed, 0), timetype=ba.TimeType.REAL, - repeat=True) - btn = ba.buttonwidget(parent=self._root_widget, - position=(h - b_size - b_buffer, - v - b_size - b_buffer + v_offs), - button_type='square', - size=(b_size, b_size), - label='', - autoselect=True, - on_activate_call=ba.Call( - self._change_replay_speed, -1)) + repeat=True, + ) + btn = ba.buttonwidget( + parent=self._root_widget, + position=( + h - b_size - b_buffer, + v - b_size - b_buffer + v_offs, + ), + button_type='square', + size=(b_size, b_size), + label='', + autoselect=True, + on_activate_call=ba.Call(self._change_replay_speed, -1), + ) ba.textwidget( parent=self._root_widget, draw_controller=btn, text='-', - position=(h - b_size * 0.5 - b_buffer, - v - b_size * 0.5 - b_buffer + 5 * t_scale + v_offs), + position=( + h - b_size * 0.5 - b_buffer, + v - b_size * 0.5 - b_buffer + 5 * t_scale + v_offs, + ), h_align='center', v_align='center', size=(0, 0), - scale=3.0 * t_scale) + scale=3.0 * t_scale, + ) btn = ba.buttonwidget( parent=self._root_widget, position=(h + b_buffer, v - b_size - b_buffer + v_offs), @@ -402,21 +489,25 @@ class MainMenuWindow(ba.Window): size=(b_size, b_size), label='', autoselect=True, - on_activate_call=ba.Call(self._change_replay_speed, 1)) + on_activate_call=ba.Call(self._change_replay_speed, 1), + ) ba.textwidget( parent=self._root_widget, draw_controller=btn, text='+', - position=(h + b_size * 0.5 + b_buffer, - v - b_size * 0.5 - b_buffer + 5 * t_scale + v_offs), + position=( + h + b_size * 0.5 + b_buffer, + v - b_size * 0.5 - b_buffer + 5 * t_scale + v_offs, + ), h_align='center', v_align='center', size=(0, 0), - scale=3.0 * t_scale) + scale=3.0 * t_scale, + ) def _refresh_not_in_game( - self, positions: list[tuple[float, float, - float]]) -> tuple[float, float, float]: + self, positions: list[tuple[float, float, float]] + ) -> tuple[float, float, float]: # pylint: disable=too-many-branches # pylint: disable=too-many-locals # pylint: disable=too-many-statements @@ -424,6 +515,7 @@ class MainMenuWindow(ba.Window): self._tdelay = 2.0 self._t_delay_inc = 0.02 self._t_delay_play = 1.7 + self._next_refresh_allow_time = ba.time(ba.TimeType.REAL) + 2.01 ba.app.did_menu_intro = True self._width = 400.0 self._height = 200.0 @@ -436,7 +528,8 @@ class MainMenuWindow(ba.Window): else: account_type_name = ba.Lstr( resource='notSignedInText', - fallback_resource='accountSettingsWindow.titleText') + fallback_resource='accountSettingsWindow.titleText', + ) account_type_icon = None account_textcolor = (1.0, 0.2, 0.2) account_type_icon_color = (1.0, 1.0, 1.0) @@ -480,23 +573,32 @@ class MainMenuWindow(ba.Window): self._button_height *= 1.2 button_spacing = 1.1 spc = self._button_width * small_button_scale * button_spacing - ba.containerwidget(edit=self._root_widget, - size=(self._width, self._height), - background=False, - scale=root_widget_scale) + ba.containerwidget( + edit=self._root_widget, + size=(self._width, self._height), + background=False, + scale=root_widget_scale, + ) assert not positions positions.append((self._width * 0.5, button_y_offs, 1.7)) x_offs = self._width * 0.5 - (spc * (b_count - 1) * 0.5) + (spc * 0.5) for i in range(b_count - 1): positions.append( - (x_offs + spc * i - 1.0, button_y_offs + button_y_offs2, - small_button_scale)) + ( + x_offs + spc * i - 1.0, + button_y_offs + button_y_offs2, + small_button_scale, + ) + ) # In kiosk mode, provide a button to get back to the kiosk menu. if ba.app.demo_mode or ba.app.arcade_mode: h, v, scale = positions[self._p_index] this_b_width = self._button_width * 0.4 * scale - demo_menu_delay = 0.0 if self._t_delay_play == 0.0 else max( - 0, self._t_delay_play + 0.1) + demo_menu_delay = ( + 0.0 + if self._t_delay_play == 0.0 + else max(0, self._t_delay_play + 0.1) + ) self._demo_menu_button = ba.buttonwidget( parent=self._root_widget, position=(self._width * 0.5 - this_b_width * 0.5, v + 90), @@ -504,19 +606,31 @@ class MainMenuWindow(ba.Window): autoselect=True, color=(0.45, 0.55, 0.45), textcolor=(0.7, 0.8, 0.7), - label=ba.Lstr(resource='modeArcadeText' if ba.app. - arcade_mode else 'modeDemoText'), + label=ba.Lstr( + resource='modeArcadeText' + if ba.app.arcade_mode + else 'modeDemoText' + ), transition_delay=demo_menu_delay, - on_activate_call=self._demo_menu_press) + on_activate_call=self._demo_menu_press, + ) else: self._demo_menu_button = None uiscale = ba.app.ui.uiscale - foof = (-1 if uiscale is ba.UIScale.SMALL else - 1 if uiscale is ba.UIScale.MEDIUM else 3) + foof = ( + -1 + if uiscale is ba.UIScale.SMALL + else 1 + if uiscale is ba.UIScale.MEDIUM + else 3 + ) h, v, scale = positions[self._p_index] v = v + foof - gather_delay = 0.0 if self._t_delay_play == 0.0 else max( - 0.0, self._t_delay_play + 0.1) + gather_delay = ( + 0.0 + if self._t_delay_play == 0.0 + else max(0.0, self._t_delay_play + 0.1) + ) assert play_button_width is not None assert play_button_height is not None this_h = h - play_button_width * 0.5 * scale - 40 * scale @@ -530,26 +644,30 @@ class MainMenuWindow(ba.Window): button_type='square', label='', transition_delay=gather_delay, - on_activate_call=self._gather_press) - ba.textwidget(parent=self._root_widget, - position=(this_h, v + self._button_height * 0.33), - size=(0, 0), - scale=0.75, - transition_delay=gather_delay, - draw_controller=btn, - color=(0.75, 1.0, 0.7), - maxwidth=self._button_width * 0.33, - text=ba.Lstr(resource='gatherWindow.titleText'), - h_align='center', - v_align='center') + on_activate_call=self._gather_press, + ) + ba.textwidget( + parent=self._root_widget, + position=(this_h, v + self._button_height * 0.33), + size=(0, 0), + scale=0.75, + transition_delay=gather_delay, + draw_controller=btn, + color=(0.75, 1.0, 0.7), + maxwidth=self._button_width * 0.33, + text=ba.Lstr(resource='gatherWindow.titleText'), + h_align='center', + v_align='center', + ) icon_size = this_b_width * 0.6 - ba.imagewidget(parent=self._root_widget, - size=(icon_size, icon_size), - draw_controller=btn, - transition_delay=gather_delay, - position=(this_h - 0.5 * icon_size, - v + 0.31 * this_b_height), - texture=ba.gettexture('usersButton')) + ba.imagewidget( + parent=self._root_widget, + size=(icon_size, icon_size), + draw_controller=btn, + transition_delay=gather_delay, + position=(this_h - 0.5 * icon_size, v + 0.31 * this_b_height), + texture=ba.gettexture('usersButton'), + ) # Play button. h, v, scale = positions[self._p_index] @@ -563,13 +681,19 @@ class MainMenuWindow(ba.Window): text_res_scale=2.0, label=ba.Lstr(resource='playText'), transition_delay=self._t_delay_play, - on_activate_call=self._play_press) - ba.containerwidget(edit=self._root_widget, - start_button=start_button, - selected_child=start_button) + on_activate_call=self._play_press, + ) + ba.containerwidget( + edit=self._root_widget, + start_button=start_button, + selected_child=start_button, + ) v = v + foof - watch_delay = 0.0 if self._t_delay_play == 0.0 else max( - 0.0, self._t_delay_play - 0.1) + watch_delay = ( + 0.0 + if self._t_delay_play == 0.0 + else max(0.0, self._t_delay_play - 0.1) + ) this_h = h + play_button_width * 0.5 * scale + 40 * scale this_b_width = self._button_width * 0.25 * scale this_b_height = self._button_height * 0.82 * scale @@ -581,31 +705,35 @@ class MainMenuWindow(ba.Window): button_type='square', label='', transition_delay=watch_delay, - on_activate_call=self._watch_press) - ba.textwidget(parent=self._root_widget, - position=(this_h, v + self._button_height * 0.33), - size=(0, 0), - scale=0.75, - transition_delay=watch_delay, - color=(0.75, 1.0, 0.7), - draw_controller=btn, - maxwidth=self._button_width * 0.33, - text=ba.Lstr(resource='watchWindow.titleText'), - h_align='center', - v_align='center') + on_activate_call=self._watch_press, + ) + ba.textwidget( + parent=self._root_widget, + position=(this_h, v + self._button_height * 0.33), + size=(0, 0), + scale=0.75, + transition_delay=watch_delay, + color=(0.75, 1.0, 0.7), + draw_controller=btn, + maxwidth=self._button_width * 0.33, + text=ba.Lstr(resource='watchWindow.titleText'), + h_align='center', + v_align='center', + ) icon_size = this_b_width * 0.55 - ba.imagewidget(parent=self._root_widget, - size=(icon_size, icon_size), - draw_controller=btn, - transition_delay=watch_delay, - position=(this_h - 0.5 * icon_size, - v + 0.33 * this_b_height), - texture=ba.gettexture('tv')) + ba.imagewidget( + parent=self._root_widget, + size=(icon_size, icon_size), + draw_controller=btn, + transition_delay=watch_delay, + position=(this_h - 0.5 * icon_size, v + 0.33 * this_b_height), + texture=ba.gettexture('tv'), + ) if not self._in_game and enable_account_button: this_b_width = self._button_width h, v, scale = positions[self._p_index] self._p_index += 1 - self._gc_button = ba.buttonwidget( + self._account_button = ba.buttonwidget( parent=self._root_widget, position=(h - this_b_width * 0.5 * scale, v), size=(this_b_width, self._button_height), @@ -617,23 +745,32 @@ class MainMenuWindow(ba.Window): icon=account_type_icon, icon_color=account_type_icon_color, transition_delay=self._tdelay, - enable_sound=account_type_enable_button_sound) + enable_sound=account_type_enable_button_sound, + ) # Scattered eggs on easter. - if ba.internal.get_v1_account_misc_read_val( - 'easter', False) and not self._in_game: + if ( + ba.internal.get_v1_account_misc_read_val('easter', False) + and not self._in_game + ): icon_size = 32 - ba.imagewidget(parent=self._root_widget, - position=(h - icon_size * 0.5 + 35, - v + self._button_height * scale - - icon_size * 0.24 + 1.5), - transition_delay=self._tdelay, - size=(icon_size, icon_size), - texture=ba.gettexture('egg2'), - tilt_scale=0.0) + ba.imagewidget( + parent=self._root_widget, + position=( + h - icon_size * 0.5 + 35, + v + + self._button_height * scale + - icon_size * 0.24 + + 1.5, + ), + transition_delay=self._tdelay, + size=(icon_size, icon_size), + texture=ba.gettexture('egg2'), + tilt_scale=0.0, + ) self._tdelay += self._t_delay_inc else: - self._gc_button = None + self._account_button = None # How-to-play button. h, v, scale = positions[self._p_index] @@ -646,21 +783,27 @@ class MainMenuWindow(ba.Window): size=(self._button_width, self._button_height), label=ba.Lstr(resource=self._r + '.howToPlayText'), transition_delay=self._tdelay, - on_activate_call=self._howtoplay) + on_activate_call=self._howtoplay, + ) self._how_to_play_button = btn # Scattered eggs on easter. - if ba.internal.get_v1_account_misc_read_val( - 'easter', False) and not self._in_game: + if ( + ba.internal.get_v1_account_misc_read_val('easter', False) + and not self._in_game + ): icon_size = 28 - ba.imagewidget(parent=self._root_widget, - position=(h - icon_size * 0.5 + 30, - v + self._button_height * scale - - icon_size * 0.24 + 1.5), - transition_delay=self._tdelay, - size=(icon_size, icon_size), - texture=ba.gettexture('egg4'), - tilt_scale=0.0) + ba.imagewidget( + parent=self._root_widget, + position=( + h - icon_size * 0.5 + 30, + v + self._button_height * scale - icon_size * 0.24 + 1.5, + ), + transition_delay=self._tdelay, + size=(icon_size, icon_size), + texture=ba.gettexture('egg4'), + tilt_scale=0.0, + ) # Credits button. self._tdelay += self._t_delay_inc h, v, scale = positions[self._p_index] @@ -673,13 +816,14 @@ class MainMenuWindow(ba.Window): label=ba.Lstr(resource=self._r + '.creditsText'), scale=scale, transition_delay=self._tdelay, - on_activate_call=self._credits) + on_activate_call=self._credits, + ) self._tdelay += self._t_delay_inc return h, v, scale def _refresh_in_game( - self, positions: list[tuple[float, float, - float]]) -> tuple[float, float, float]: + self, positions: list[tuple[float, float, float]] + ) -> tuple[float, float, float]: # pylint: disable=too-many-branches # pylint: disable=too-many-locals # pylint: disable=too-many-statements @@ -689,15 +833,21 @@ class MainMenuWindow(ba.Window): try: custom_menu_entries = session.get_custom_menu_entries() for cme in custom_menu_entries: - if (not isinstance(cme, dict) or 'label' not in cme - or not isinstance(cme['label'], (str, ba.Lstr)) - or 'call' not in cme or not callable(cme['call'])): - raise ValueError('invalid custom menu entry: ' + - str(cme)) + if ( + not isinstance(cme, dict) + or 'label' not in cme + or not isinstance(cme['label'], (str, ba.Lstr)) + or 'call' not in cme + or not callable(cme['call']) + ): + raise ValueError( + 'invalid custom menu entry: ' + str(cme) + ) except Exception: custom_menu_entries = [] ba.print_exception( - f'Error getting custom menu entries for {session}') + f'Error getting custom menu entries for {session}' + ) self._width = 250.0 self._height = 250.0 if self._input_player else 180.0 if (self._is_demo or self._is_arcade) and self._input_player: @@ -712,10 +862,16 @@ class MainMenuWindow(ba.Window): ba.containerwidget( edit=self._root_widget, size=(self._width, self._height), - scale=(2.15 if uiscale is ba.UIScale.SMALL else - 1.6 if uiscale is ba.UIScale.MEDIUM else 1.0)) + scale=( + 2.15 + if uiscale is ba.UIScale.SMALL + else 1.6 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + ) h = 125.0 - v = (self._height - 80.0 if self._input_player else self._height - 60) + v = self._height - 80.0 if self._input_player else self._height - 60 h_offset = 0 d_h_offset = 0 v_offset = -50 @@ -732,24 +888,28 @@ class MainMenuWindow(ba.Window): player_name = self._input_player.getname() h, v, scale = positions[self._p_index] v += 35 - ba.textwidget(parent=self._root_widget, - position=(h - self._button_width / 2, v), - size=(self._button_width, self._button_height), - color=(1, 1, 1, 0.5), - scale=0.7, - h_align='center', - text=ba.Lstr(value=player_name)) + ba.textwidget( + parent=self._root_widget, + position=(h - self._button_width / 2, v), + size=(self._button_width, self._button_height), + color=(1, 1, 1, 0.5), + scale=0.7, + h_align='center', + text=ba.Lstr(value=player_name), + ) else: player_name = '' h, v, scale = positions[self._p_index] self._p_index += 1 - btn = ba.buttonwidget(parent=self._root_widget, - position=(h - self._button_width / 2, v), - size=(self._button_width, self._button_height), - scale=scale, - label=ba.Lstr(resource=self._r + '.resumeText'), - autoselect=self._use_autoselect, - on_activate_call=self._resume) + btn = ba.buttonwidget( + parent=self._root_widget, + position=(h - self._button_width / 2, v), + size=(self._button_width, self._button_height), + scale=scale, + label=ba.Lstr(resource=self._r + '.resumeText'), + autoselect=self._use_autoselect, + on_activate_call=self._resume, + ) ba.containerwidget(edit=self._root_widget, cancel_button=btn) # Add any custom options defined by the current game. @@ -766,54 +926,71 @@ class MainMenuWindow(ba.Window): else: call = ba.Call(entry['call'], ba.WeakCall(self._resume)) - ba.buttonwidget(parent=self._root_widget, - position=(h - self._button_width / 2, v), - size=(self._button_width, self._button_height), - scale=scale, - on_activate_call=call, - label=entry['label'], - autoselect=self._use_autoselect) + ba.buttonwidget( + parent=self._root_widget, + position=(h - self._button_width / 2, v), + size=(self._button_width, self._button_height), + scale=scale, + on_activate_call=call, + label=entry['label'], + autoselect=self._use_autoselect, + ) # Add a 'leave' button if the menu-owner has a player. - if ((self._input_player or self._connected_to_remote_player) - and not (self._is_demo or self._is_arcade)): + if (self._input_player or self._connected_to_remote_player) and not ( + self._is_demo or self._is_arcade + ): h, v, scale = positions[self._p_index] self._p_index += 1 - btn = ba.buttonwidget(parent=self._root_widget, - position=(h - self._button_width / 2, v), - size=(self._button_width, - self._button_height), - scale=scale, - on_activate_call=self._leave, - label='', - autoselect=self._use_autoselect) + btn = ba.buttonwidget( + parent=self._root_widget, + position=(h - self._button_width / 2, v), + size=(self._button_width, self._button_height), + scale=scale, + on_activate_call=self._leave, + label='', + autoselect=self._use_autoselect, + ) - if (player_name != '' and player_name[0] != '<' - and player_name[-1] != '>'): - txt = ba.Lstr(resource=self._r + '.justPlayerText', - subs=[('${NAME}', player_name)]) + if ( + player_name != '' + and player_name[0] != '<' + and player_name[-1] != '>' + ): + txt = ba.Lstr( + resource=self._r + '.justPlayerText', + subs=[('${NAME}', player_name)], + ) else: txt = ba.Lstr(value=player_name) - ba.textwidget(parent=self._root_widget, - position=(h, v + self._button_height * - (0.64 if player_name != '' else 0.5)), - size=(0, 0), - text=ba.Lstr(resource=self._r + '.leaveGameText'), - scale=(0.83 if player_name != '' else 1.0), - color=(0.75, 1.0, 0.7), - h_align='center', - v_align='center', - draw_controller=btn, - maxwidth=self._button_width * 0.9) - ba.textwidget(parent=self._root_widget, - position=(h, v + self._button_height * 0.27), - size=(0, 0), - text=txt, - color=(0.75, 1.0, 0.7), - h_align='center', - v_align='center', - draw_controller=btn, - scale=0.45, - maxwidth=self._button_width * 0.9) + ba.textwidget( + parent=self._root_widget, + position=( + h, + v + + self._button_height + * (0.64 if player_name != '' else 0.5), + ), + size=(0, 0), + text=ba.Lstr(resource=self._r + '.leaveGameText'), + scale=(0.83 if player_name != '' else 1.0), + color=(0.75, 1.0, 0.7), + h_align='center', + v_align='center', + draw_controller=btn, + maxwidth=self._button_width * 0.9, + ) + ba.textwidget( + parent=self._root_widget, + position=(h, v + self._button_height * 0.27), + size=(0, 0), + text=txt, + color=(0.75, 1.0, 0.7), + h_align='center', + v_align='center', + draw_controller=btn, + scale=0.45, + maxwidth=self._button_width * 0.9, + ) return h, v, scale def _change_replay_speed(self, offs: int) -> None: @@ -822,38 +999,50 @@ class MainMenuWindow(ba.Window): print('_change_replay_speed called without widget') return ba.internal.set_replay_speed_exponent( - ba.internal.get_replay_speed_exponent() + offs) + ba.internal.get_replay_speed_exponent() + offs + ) actual_speed = pow(2.0, ba.internal.get_replay_speed_exponent()) - ba.textwidget(edit=self._replay_speed_text, - text=ba.Lstr(resource='watchWindow.playbackSpeedText', - subs=[('${SPEED}', str(actual_speed))])) + ba.textwidget( + edit=self._replay_speed_text, + text=ba.Lstr( + resource='watchWindow.playbackSpeedText', + subs=[('${SPEED}', str(actual_speed))], + ), + ) def _quit(self) -> None: # pylint: disable=cyclic-import from bastd.ui.confirm import QuitWindow + QuitWindow(origin_widget=self._quit_button) def _demo_menu_press(self) -> None: # pylint: disable=cyclic-import from bastd.ui.kiosk import KioskWindow + self._save_state() ba.containerwidget(edit=self._root_widget, transition='out_right') ba.app.ui.set_main_menu_window( - KioskWindow(transition='in_left').get_root_widget()) + KioskWindow(transition='in_left').get_root_widget() + ) def _show_account_window(self) -> None: # pylint: disable=cyclic-import from bastd.ui.account.settings import AccountSettingsWindow + self._save_state() ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.ui.set_main_menu_window( AccountSettingsWindow( - origin_widget=self._gc_button).get_root_widget()) + origin_widget=self._account_button + ).get_root_widget() + ) def _on_store_pressed(self) -> None: # pylint: disable=cyclic-import from bastd.ui.store.browser import StoreBrowserWindow from bastd.ui.account import show_sign_in_prompt + if ba.internal.get_v1_account_state() != 'signed_in': show_sign_in_prompt() return @@ -861,18 +1050,42 @@ class MainMenuWindow(ba.Window): ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.ui.set_main_menu_window( StoreBrowserWindow( - origin_widget=self._store_button).get_root_widget()) + origin_widget=self._store_button + ).get_root_widget() + ) + + def _is_benchmark(self) -> bool: + session = ba.internal.get_foreground_host_session() + return ( + getattr(session, 'benchmark_type', None) == 'cpu' + or ba.app.stress_test_reset_timer is not None + ) def _confirm_end_game(self) -> None: # pylint: disable=cyclic-import from bastd.ui.confirm import ConfirmWindow + # FIXME: Currently we crash calling this on client-sessions. # Select cancel by default; this occasionally gets called by accident # in a fit of button mashing and this will help reduce damage. - ConfirmWindow(ba.Lstr(resource=self._r + '.exitToMenuText'), - self._end_game, - cancel_is_selected=True) + ConfirmWindow( + ba.Lstr(resource=self._r + '.exitToMenuText'), + self._end_game, + cancel_is_selected=True, + ) + + def _confirm_end_test(self) -> None: + # pylint: disable=cyclic-import + from bastd.ui.confirm import ConfirmWindow + + # Select cancel by default; this occasionally gets called by accident + # in a fit of button mashing and this will help reduce damage. + ConfirmWindow( + ba.Lstr(resource=self._r + '.exitToMenuText'), + self._end_game, + cancel_is_selected=True, + ) def _confirm_end_replay(self) -> None: # pylint: disable=cyclic-import @@ -880,9 +1093,11 @@ class MainMenuWindow(ba.Window): # Select cancel by default; this occasionally gets called by accident # in a fit of button mashing and this will help reduce damage. - ConfirmWindow(ba.Lstr(resource=self._r + '.exitToMenuText'), - self._end_game, - cancel_is_selected=True) + ConfirmWindow( + ba.Lstr(resource=self._r + '.exitToMenuText'), + self._end_game, + cancel_is_selected=True, + ) def _confirm_leave_party(self) -> None: # pylint: disable=cyclic-import @@ -890,9 +1105,11 @@ class MainMenuWindow(ba.Window): # Select cancel by default; this occasionally gets called by accident # in a fit of button mashing and this will help reduce damage. - ConfirmWindow(ba.Lstr(resource=self._r + '.leavePartyConfirmText'), - self._leave_party, - cancel_is_selected=True) + ConfirmWindow( + ba.Lstr(resource=self._r + '.leavePartyConfirmText'), + self._leave_party, + cancel_is_selected=True, + ) def _leave_party(self) -> None: ba.internal.disconnect_from_host() @@ -914,30 +1131,38 @@ class MainMenuWindow(ba.Window): def _credits(self) -> None: # pylint: disable=cyclic-import from bastd.ui.creditslist import CreditsListWindow + self._save_state() ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.ui.set_main_menu_window( CreditsListWindow( - origin_widget=self._credits_button).get_root_widget()) + origin_widget=self._credits_button + ).get_root_widget() + ) def _howtoplay(self) -> None: # pylint: disable=cyclic-import from bastd.ui.helpui import HelpWindow + self._save_state() ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.ui.set_main_menu_window( HelpWindow( - main_menu=True, - origin_widget=self._how_to_play_button).get_root_widget()) + main_menu=True, origin_widget=self._how_to_play_button + ).get_root_widget() + ) def _settings(self) -> None: # pylint: disable=cyclic-import from bastd.ui.settings.allsettings import AllSettingsWindow + self._save_state() ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.ui.set_main_menu_window( AllSettingsWindow( - origin_widget=self._settings_button).get_root_widget()) + origin_widget=self._settings_button + ).get_root_widget() + ) def _resume_and_call(self, call: Callable[[], Any]) -> None: self._resume() @@ -965,8 +1190,8 @@ class MainMenuWindow(ba.Window): ba.app.ui.main_menu_selection = 'Credits' elif sel == self._settings_button: ba.app.ui.main_menu_selection = 'Settings' - elif sel == self._gc_button: - ba.app.ui.main_menu_selection = 'GameService' + elif sel == self._account_button: + ba.app.ui.main_menu_selection = 'Account' elif sel == self._store_button: ba.app.ui.main_menu_selection = 'Store' elif sel == self._quit_button: @@ -997,8 +1222,8 @@ class MainMenuWindow(ba.Window): sel = self._credits_button elif sel_name == 'Settings': sel = self._settings_button - elif sel_name == 'GameService': - sel = self._gc_button + elif sel_name == 'Account': + sel = self._account_button elif sel_name == 'Store': sel = self._store_button elif sel_name == 'Quit': @@ -1013,28 +1238,34 @@ class MainMenuWindow(ba.Window): def _gather_press(self) -> None: # pylint: disable=cyclic-import from bastd.ui.gather import GatherWindow + self._save_state() ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.ui.set_main_menu_window( - GatherWindow(origin_widget=self._gather_button).get_root_widget()) + GatherWindow(origin_widget=self._gather_button).get_root_widget() + ) def _watch_press(self) -> None: # pylint: disable=cyclic-import from bastd.ui.watch import WatchWindow + self._save_state() ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.ui.set_main_menu_window( - WatchWindow(origin_widget=self._watch_button).get_root_widget()) + WatchWindow(origin_widget=self._watch_button).get_root_widget() + ) def _play_press(self) -> None: # pylint: disable=cyclic-import from bastd.ui.play import PlayWindow + self._save_state() ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.ui.selecting_private_party_playlist = False ba.app.ui.set_main_menu_window( - PlayWindow(origin_widget=self._start_button).get_root_widget()) + PlayWindow(origin_widget=self._start_button).get_root_widget() + ) def _resume(self) -> None: ba.app.resume() diff --git a/dist/ba_data/python/bastd/ui/onscreenkeyboard.py b/dist/ba_data/python/bastd/ui/onscreenkeyboard.py index 842ecd1..19d6c95 100644 --- a/dist/ba_data/python/bastd/ui/onscreenkeyboard.py +++ b/dist/ba_data/python/bastd/ui/onscreenkeyboard.py @@ -22,35 +22,53 @@ class OnScreenKeyboardWindow(ba.Window): self._height = 400 uiscale = ba.app.ui.uiscale top_extra = 20 if uiscale is ba.UIScale.SMALL else 0 - super().__init__(root_widget=ba.containerwidget( - parent=ba.internal.get_special_widget('overlay_stack'), - size=(self._width, self._height + top_extra), - transition='in_scale', - scale_origin_stack_offset=self._target_text. - get_screen_space_center(), - scale=(2.0 if uiscale is ba.UIScale.SMALL else - 1.5 if uiscale is ba.UIScale.MEDIUM else 1.0), - stack_offset=(0, 0) if uiscale is ba.UIScale.SMALL else ( - 0, 0) if uiscale is ba.UIScale.MEDIUM else (0, 0))) - self._done_button = ba.buttonwidget(parent=self._root_widget, - position=(self._width - 200, 44), - size=(140, 60), - autoselect=True, - label=ba.Lstr(resource='doneText'), - on_activate_call=self._done) - ba.containerwidget(edit=self._root_widget, - on_cancel_call=self._cancel, - start_button=self._done_button) + super().__init__( + root_widget=ba.containerwidget( + parent=ba.internal.get_special_widget('overlay_stack'), + size=(self._width, self._height + top_extra), + transition='in_scale', + scale_origin_stack_offset=( + self._target_text.get_screen_space_center() + ), + scale=( + 2.0 + if uiscale is ba.UIScale.SMALL + else 1.5 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + stack_offset=(0, 0) + if uiscale is ba.UIScale.SMALL + else (0, 0) + if uiscale is ba.UIScale.MEDIUM + else (0, 0), + ) + ) + self._done_button = ba.buttonwidget( + parent=self._root_widget, + position=(self._width - 200, 44), + size=(140, 60), + autoselect=True, + label=ba.Lstr(resource='doneText'), + on_activate_call=self._done, + ) + ba.containerwidget( + edit=self._root_widget, + on_cancel_call=self._cancel, + start_button=self._done_button, + ) - ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, self._height - 41), - size=(0, 0), - scale=0.95, - text=label, - maxwidth=self._width - 140, - color=ba.app.ui.title_color, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, self._height - 41), + size=(0, 0), + scale=0.95, + text=label, + maxwidth=self._width - 140, + color=ba.app.ui.title_color, + h_align='center', + v_align='center', + ) self._text_field = ba.textwidget( parent=self._root_widget, @@ -64,7 +82,8 @@ class OnScreenKeyboardWindow(ba.Window): editable=True, maxwidth=self._width - 175, force_internal_editing=True, - always_show_carat=True) + always_show_carat=True, + ) self._key_color_lit = (1.4, 1.2, 1.4) self._key_color = (0.69, 0.6, 0.74) @@ -165,8 +184,9 @@ class OnScreenKeyboardWindow(ba.Window): color=key_color_dark, label=ba.charstr(ba.SpecialChar.DELETE), button_type='square', - on_activate_call=self._del) - v -= (key_height + 9) + on_activate_call=self._del, + ) + v -= key_height + 9 # Do space bar and stuff. if row_num == 2: if self._num_mode_button is None: @@ -207,13 +227,16 @@ class OnScreenKeyboardWindow(ba.Window): textcolor=key_textcolor, color=key_color_dark, label=ba.Lstr(resource='spaceKeyText'), - on_activate_call=ba.Call(self._type_char, ' ')) + on_activate_call=ba.Call(self._type_char, ' '), + ) # Show change instructions only if we have more than one # keyboard option. - keyboards = (ba.app.meta.scanresults.exports_of_class( - ba.Keyboard) if ba.app.meta.scanresults is not None - else []) + keyboards = ( + ba.app.meta.scanresults.exports_of_class(ba.Keyboard) + if ba.app.meta.scanresults is not None + else [] + ) if len(keyboards) > 1: ba.textwidget( parent=self._root_widget, @@ -221,26 +244,30 @@ class OnScreenKeyboardWindow(ba.Window): position=(210, v - 70), size=(key_width * 6.1, key_height + 15), text=ba.Lstr( - resource='keyboardChangeInstructionsText'), - scale=0.75) + resource='keyboardChangeInstructionsText' + ), + scale=0.75, + ) btn2 = self._space_button btn3 = self._emoji_button ba.widget(edit=btn1, right_widget=btn2, left_widget=btn3) - ba.widget(edit=btn2, - left_widget=btn1, - right_widget=self._done_button) + ba.widget( + edit=btn2, left_widget=btn1, right_widget=self._done_button + ) ba.widget(edit=btn3, left_widget=btn1) ba.widget(edit=self._done_button, left_widget=btn2) - ba.containerwidget(edit=self._root_widget, - selected_child=self._char_keys[14]) + ba.containerwidget( + edit=self._root_widget, selected_child=self._char_keys[14] + ) self._refresh() def _get_keyboard(self) -> ba.Keyboard: assert ba.app.meta.scanresults is not None - classname = ba.app.meta.scanresults.exports_of_class( - ba.Keyboard)[self._keyboard_index] + classname = ba.app.meta.scanresults.exports_of_class(ba.Keyboard)[ + self._keyboard_index + ] kbclass = ba.getclass(classname, ba.Keyboard) return kbclass() @@ -250,34 +277,47 @@ class OnScreenKeyboardWindow(ba.Window): chars = list(self._chars) if self._mode == 'caps': chars = [c.upper() for c in chars] - ba.buttonwidget(edit=self._shift_button, - color=self._key_color_lit - if self._mode == 'caps' else self._key_color_dark, - label=ba.charstr(ba.SpecialChar.SHIFT), - on_activate_call=self._shift) - ba.buttonwidget(edit=self._num_mode_button, - label='123#&*', - on_activate_call=self._num_mode) - ba.buttonwidget(edit=self._emoji_button, - color=self._key_color_dark, - label=ba.charstr(ba.SpecialChar.LOGO_FLAT), - on_activate_call=self._next_mode) + ba.buttonwidget( + edit=self._shift_button, + color=self._key_color_lit + if self._mode == 'caps' + else self._key_color_dark, + label=ba.charstr(ba.SpecialChar.SHIFT), + on_activate_call=self._shift, + ) + ba.buttonwidget( + edit=self._num_mode_button, + label='123#&*', + on_activate_call=self._num_mode, + ) + ba.buttonwidget( + edit=self._emoji_button, + color=self._key_color_dark, + label=ba.charstr(ba.SpecialChar.LOGO_FLAT), + on_activate_call=self._next_mode, + ) else: if self._mode == 'num': chars = list(self._keyboard.nums) else: chars = list(self._keyboard.pages[self._mode]) - ba.buttonwidget(edit=self._shift_button, - color=self._key_color_dark, - label='', - on_activate_call=self._null_press) - ba.buttonwidget(edit=self._num_mode_button, - label='abc', - on_activate_call=self._abc_mode) - ba.buttonwidget(edit=self._emoji_button, - color=self._key_color_dark, - label=ba.charstr(ba.SpecialChar.LOGO_FLAT), - on_activate_call=self._next_mode) + ba.buttonwidget( + edit=self._shift_button, + color=self._key_color_dark, + label='', + on_activate_call=self._null_press, + ) + ba.buttonwidget( + edit=self._num_mode_button, + label='abc', + on_activate_call=self._abc_mode, + ) + ba.buttonwidget( + edit=self._emoji_button, + color=self._key_color_dark, + label=ba.charstr(ba.SpecialChar.LOGO_FLAT), + on_activate_call=self._next_mode, + ) for i, btn in enumerate(self._char_keys): assert chars is not None @@ -291,12 +331,15 @@ class OnScreenKeyboardWindow(ba.Window): f' "{self._keyboard.name}" is incorrect:' f' {len(chars)} != {len(self._chars)}' f' (size of default "normal" page)', - once=True) - ba.buttonwidget(edit=btn, - label=chars[i] if have_char else ' ', - on_activate_call=ba.Call( - self._type_char, - chars[i] if have_char else ' ')) + once=True, + ) + ba.buttonwidget( + edit=btn, + label=chars[i] if have_char else ' ', + on_activate_call=ba.Call( + self._type_char, chars[i] if have_char else ' ' + ), + ) def _null_press(self) -> None: ba.playsound(self._click_sound) @@ -325,12 +368,18 @@ class OnScreenKeyboardWindow(ba.Window): self._load_keyboard() if len(kbexports) < 2: ba.playsound(ba.getsound('error')) - ba.screenmessage(ba.Lstr(resource='keyboardNoOthersAvailableText'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr(resource='keyboardNoOthersAvailableText'), + color=(1, 0, 0), + ) else: - ba.screenmessage(ba.Lstr(resource='keyboardSwitchText', - subs=[('${NAME}', self._keyboard.name)]), - color=(0, 1, 0)) + ba.screenmessage( + ba.Lstr( + resource='keyboardSwitchText', + subs=[('${NAME}', self._keyboard.name)], + ), + color=(0, 1, 0), + ) def _shift(self) -> None: ba.playsound(self._click_sound) @@ -354,8 +403,10 @@ class OnScreenKeyboardWindow(ba.Window): def _type_char(self, char: str) -> None: ba.playsound(self._click_sound) if char.isspace(): - if (ba.time(ba.TimeType.REAL) - self._last_space_press < - self._double_space_interval): + if ( + ba.time(ba.TimeType.REAL) - self._last_space_press + < self._double_space_interval + ): self._last_space_press = 0 self._next_keyboard() self._del() # We typed unneeded space around 1s ago. @@ -380,6 +431,7 @@ class OnScreenKeyboardWindow(ba.Window): def _done(self) -> None: ba.containerwidget(edit=self._root_widget, transition='out_scale') if self._target_text: - ba.textwidget(edit=self._target_text, - text=cast(str, - ba.textwidget(query=self._text_field))) + ba.textwidget( + edit=self._target_text, + text=cast(str, ba.textwidget(query=self._text_field)), + ) diff --git a/dist/ba_data/python/bastd/ui/party.py b/dist/ba_data/python/bastd/ui/party.py index 7de7b62..f2ea820 100644 --- a/dist/ba_data/python/bastd/ui/party.py +++ b/dist/ba_data/python/bastd/ui/party.py @@ -29,32 +29,51 @@ class PartyWindow(ba.Window): self._popup_party_member_is_host: bool | None = None self._width = 500 uiscale = ba.app.ui.uiscale - self._height = (365 if uiscale is ba.UIScale.SMALL else - 480 if uiscale is ba.UIScale.MEDIUM else 600) - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height), - transition='in_scale', - color=(0.40, 0.55, 0.20), - parent=ba.internal.get_special_widget('overlay_stack'), - on_outside_click_call=self.close_with_sound, - scale_origin_stack_offset=origin, - scale=(2.0 if uiscale is ba.UIScale.SMALL else - 1.35 if uiscale is ba.UIScale.MEDIUM else 1.0), - stack_offset=(0, -10) if uiscale is ba.UIScale.SMALL else ( - 240, 0) if uiscale is ba.UIScale.MEDIUM else (330, 20))) + self._height = ( + 365 + if uiscale is ba.UIScale.SMALL + else 480 + if uiscale is ba.UIScale.MEDIUM + else 600 + ) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height), + transition='in_scale', + color=(0.40, 0.55, 0.20), + parent=ba.internal.get_special_widget('overlay_stack'), + on_outside_click_call=self.close_with_sound, + scale_origin_stack_offset=origin, + scale=( + 2.0 + if uiscale is ba.UIScale.SMALL + else 1.35 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + stack_offset=(0, -10) + if uiscale is ba.UIScale.SMALL + else (240, 0) + if uiscale is ba.UIScale.MEDIUM + else (330, 20), + ) + ) - self._cancel_button = ba.buttonwidget(parent=self._root_widget, - scale=0.7, - position=(30, self._height - 47), - size=(50, 50), - label='', - on_activate_call=self.close, - autoselect=True, - color=(0.45, 0.63, 0.15), - icon=ba.gettexture('crossOut'), - iconscale=1.2) - ba.containerwidget(edit=self._root_widget, - cancel_button=self._cancel_button) + self._cancel_button = ba.buttonwidget( + parent=self._root_widget, + scale=0.7, + position=(30, self._height - 47), + size=(50, 50), + label='', + on_activate_call=self.close, + autoselect=True, + color=(0.45, 0.63, 0.15), + icon=ba.gettexture('crossOut'), + iconscale=1.2, + ) + ba.containerwidget( + edit=self._root_widget, cancel_button=self._cancel_button + ) self._menu_button = ba.buttonwidget( parent=self._root_widget, @@ -66,7 +85,8 @@ class PartyWindow(ba.Window): button_type='square', on_activate_call=ba.WeakCall(self._on_menu_button_press), color=(0.55, 0.73, 0.25), - iconscale=1.2) + iconscale=1.2, + ) info = ba.internal.get_connection_to_host_info() if info.get('name', '') != '': @@ -74,35 +94,38 @@ class PartyWindow(ba.Window): else: title = ba.Lstr(resource=self._r + '.titleText') - self._title_text = ba.textwidget(parent=self._root_widget, - scale=0.9, - color=(0.5, 0.7, 0.5), - text=title, - size=(0, 0), - position=(self._width * 0.5, - self._height - 29), - maxwidth=self._width * 0.7, - h_align='center', - v_align='center') + self._title_text = ba.textwidget( + parent=self._root_widget, + scale=0.9, + color=(0.5, 0.7, 0.5), + text=title, + size=(0, 0), + position=(self._width * 0.5, self._height - 29), + maxwidth=self._width * 0.7, + h_align='center', + v_align='center', + ) - self._empty_str = ba.textwidget(parent=self._root_widget, - scale=0.75, - size=(0, 0), - position=(self._width * 0.5, - self._height - 65), - maxwidth=self._width * 0.85, - h_align='center', - v_align='center') + self._empty_str = ba.textwidget( + parent=self._root_widget, + scale=0.75, + size=(0, 0), + position=(self._width * 0.5, self._height - 65), + maxwidth=self._width * 0.85, + h_align='center', + v_align='center', + ) self._scroll_width = self._width - 50 - self._scrollwidget = ba.scrollwidget(parent=self._root_widget, - size=(self._scroll_width, - self._height - 200), - position=(30, 80), - color=(0.4, 0.6, 0.3)) - self._columnwidget = ba.columnwidget(parent=self._scrollwidget, - border=2, - margin=0) + self._scrollwidget = ba.scrollwidget( + parent=self._root_widget, + size=(self._scroll_width, self._height - 200), + position=(30, 80), + color=(0.4, 0.6, 0.3), + ) + self._columnwidget = ba.columnwidget( + parent=self._scrollwidget, border=2, margin=0 + ) ba.widget(edit=self._menu_button, down_widget=self._columnwidget) self._muted_text = ba.textwidget( @@ -111,7 +134,8 @@ class PartyWindow(ba.Window): size=(0, 0), h_align='center', v_align='center', - text=ba.Lstr(resource='chatMutedText')) + text=ba.Lstr(resource='chatMutedText'), + ) self._chat_texts: list[ba.Widget] = [] # add all existing messages if chat is not muted @@ -132,32 +156,41 @@ class PartyWindow(ba.Window): description=ba.Lstr(resource=self._r + '.chatMessageText'), autoselect=True, v_align='center', - corner_scale=0.7) + corner_scale=0.7, + ) - ba.widget(edit=self._scrollwidget, - autoselect=True, - left_widget=self._cancel_button, - up_widget=self._cancel_button, - down_widget=self._text_field) - ba.widget(edit=self._columnwidget, - autoselect=True, - up_widget=self._cancel_button, - down_widget=self._text_field) + ba.widget( + edit=self._scrollwidget, + autoselect=True, + left_widget=self._cancel_button, + up_widget=self._cancel_button, + down_widget=self._text_field, + ) + ba.widget( + edit=self._columnwidget, + autoselect=True, + up_widget=self._cancel_button, + down_widget=self._text_field, + ) ba.containerwidget(edit=self._root_widget, selected_child=txt) - btn = ba.buttonwidget(parent=self._root_widget, - size=(50, 35), - label=ba.Lstr(resource=self._r + '.sendText'), - button_type='square', - autoselect=True, - position=(self._width - 70, 35), - on_activate_call=self._send_chat_message) + btn = ba.buttonwidget( + parent=self._root_widget, + size=(50, 35), + label=ba.Lstr(resource=self._r + '.sendText'), + button_type='square', + autoselect=True, + position=(self._width - 70, 35), + on_activate_call=self._send_chat_message, + ) ba.textwidget(edit=txt, on_return_press_call=btn.activate) self._name_widgets: list[ba.Widget] = [] self._roster: list[dict[str, Any]] | None = None - self._update_timer = ba.Timer(1.0, - ba.WeakCall(self._update), - repeat=True, - timetype=ba.TimeType.REAL) + self._update_timer = ba.Timer( + 1.0, + ba.WeakCall(self._update), + repeat=True, + timetype=ba.TimeType.REAL, + ) self._update() def on_chat_message(self, msg: str) -> None: @@ -166,15 +199,17 @@ class PartyWindow(ba.Window): self._add_msg(msg) def _add_msg(self, msg: str) -> None: - txt = ba.textwidget(parent=self._columnwidget, - text=msg, - h_align='left', - v_align='center', - size=(0, 13), - scale=0.55, - maxwidth=self._scroll_width * 0.94, - shadow=0.3, - flatness=1.0) + txt = ba.textwidget( + parent=self._columnwidget, + text=msg, + h_align='left', + v_align='center', + size=(0, 13), + scale=0.55, + maxwidth=self._scroll_width * 0.94, + shadow=0.3, + flatness=1.0, + ) self._chat_texts.append(txt) if len(self._chat_texts) > 40: first = self._chat_texts.pop(0) @@ -186,15 +221,22 @@ class PartyWindow(ba.Window): uiscale = ba.app.ui.uiscale popup.PopupMenuWindow( position=self._menu_button.get_screen_space_center(), - scale=(2.3 if uiscale is ba.UIScale.SMALL else - 1.65 if uiscale is ba.UIScale.MEDIUM else 1.23), + scale=( + 2.3 + if uiscale is ba.UIScale.SMALL + else 1.65 + if uiscale is ba.UIScale.MEDIUM + else 1.23 + ), choices=['unmute' if is_muted else 'mute'], choices_display=[ ba.Lstr( - resource='chatUnMuteText' if is_muted else 'chatMuteText') + resource='chatUnMuteText' if is_muted else 'chatMuteText' + ) ], current_choice='unmute' if is_muted else 'mute', - delegate=self) + delegate=self, + ) self._popup_type = 'menu' def _update(self) -> None: @@ -225,15 +267,26 @@ class PartyWindow(ba.Window): self._name_widgets = [] if not self._roster: top_section_height = 60 - ba.textwidget(edit=self._empty_str, - text=ba.Lstr(resource=self._r + '.emptyText')) - ba.scrollwidget(edit=self._scrollwidget, - size=(self._width - 50, - self._height - top_section_height - 110), - position=(30, 80)) + ba.textwidget( + edit=self._empty_str, + text=ba.Lstr(resource=self._r + '.emptyText'), + ) + ba.scrollwidget( + edit=self._scrollwidget, + size=( + self._width - 50, + self._height - top_section_height - 110, + ), + position=(30, 80), + ) else: - columns = 1 if len( - self._roster) == 1 else 2 if len(self._roster) == 2 else 3 + columns = ( + 1 + if len(self._roster) == 1 + else 2 + if len(self._roster) == 2 + else 3 + ) rows = int(math.ceil(float(len(self._roster)) / columns)) c_width = (self._width * 0.9) / max(3, columns) c_width_total = c_width * columns @@ -244,9 +297,13 @@ class PartyWindow(ba.Window): index = y * columns + x if index < len(self._roster): t_scale = 0.65 - pos = (self._width * 0.53 - c_width_total * 0.5 + - c_width * x - 23, - self._height - 65 - c_height * y - 15) + pos = ( + self._width * 0.53 + - c_width_total * 0.5 + + c_width * x + - 23, + self._height - 65 - c_height * y - 15, + ) # if there are players present for this client, use # their names as a display string instead of the @@ -255,39 +312,45 @@ class PartyWindow(ba.Window): if self._roster[index]['players']: # if there's just one, use the full name; # otherwise combine short names - if len(self._roster[index] - ['players']) == 1: + if len(self._roster[index]['players']) == 1: p_str = self._roster[index]['players'][ - 0]['name_full'] + 0 + ]['name_full'] else: - p_str = ('/'.join([ - entry['name'] for entry in - self._roster[index]['players'] - ])) + p_str = '/'.join( + [ + entry['name'] + for entry in self._roster[ + index + ]['players'] + ] + ) if len(p_str) > 25: p_str = p_str[:25] + '...' else: p_str = self._roster[index][ - 'display_string'] + 'display_string' + ] except Exception: ba.print_exception( - 'Error calcing client name str.') + 'Error calcing client name str.' + ) p_str = '???' - widget = ba.textwidget(parent=self._root_widget, - position=(pos[0], pos[1]), - scale=t_scale, - size=(c_width * 0.85, 30), - maxwidth=c_width * 0.85, - color=(1, 1, - 1) if index == 0 else - (1, 1, 1), - selectable=True, - autoselect=True, - click_activate=True, - text=ba.Lstr(value=p_str), - h_align='left', - v_align='center') + widget = ba.textwidget( + parent=self._root_widget, + position=(pos[0], pos[1]), + scale=t_scale, + size=(c_width * 0.85, 30), + maxwidth=c_width * 0.85, + color=(1, 1, 1) if index == 0 else (1, 1, 1), + selectable=True, + autoselect=True, + click_activate=True, + text=ba.Lstr(value=p_str), + h_align='left', + v_align='center', + ) self._name_widgets.append(widget) # in newer versions client_id will be present and @@ -295,22 +358,28 @@ class PartyWindow(ba.Window): # in older versions we assume the first client is # host if self._roster[index]['client_id'] is not None: - is_host = self._roster[index][ - 'client_id'] == -1 + is_host = self._roster[index]['client_id'] == -1 else: - is_host = (index == 0) + is_host = index == 0 # FIXME: Should pass client_id to these sort of # calls; not spec-string (perhaps should wait till # client_id is more readily available though). - ba.textwidget(edit=widget, - on_activate_call=ba.Call( - self._on_party_member_press, - self._roster[index]['client_id'], - is_host, widget)) - pos = (self._width * 0.53 - c_width_total * 0.5 + - c_width * x, - self._height - 65 - c_height * y) + ba.textwidget( + edit=widget, + on_activate_call=ba.Call( + self._on_party_member_press, + self._roster[index]['client_id'], + is_host, + widget, + ), + ) + pos = ( + self._width * 0.53 + - c_width_total * 0.5 + + c_width * x, + self._height - 65 - c_height * y, + ) # Make the assumption that the first roster # entry is the server. @@ -319,32 +388,43 @@ class PartyWindow(ba.Window): twd = min( c_width * 0.85, ba.internal.get_string_width( - p_str, suppress_warning=True) * - t_scale) + p_str, suppress_warning=True + ) + * t_scale, + ) self._name_widgets.append( ba.textwidget( parent=self._root_widget, - position=(pos[0] + twd + 1, - pos[1] - 0.5), + position=( + pos[0] + twd + 1, + pos[1] - 0.5, + ), size=(0, 0), h_align='left', v_align='center', maxwidth=c_width * 0.96 - twd, color=(0.1, 1, 0.1, 0.5), - text=ba.Lstr(resource=self._r + - '.hostText'), + text=ba.Lstr( + resource=self._r + '.hostText' + ), scale=0.4, shadow=0.1, - flatness=1.0)) + flatness=1.0, + ) + ) ba.textwidget(edit=self._empty_str, text='') - ba.scrollwidget(edit=self._scrollwidget, - size=(self._width - 50, - max(100, self._height - 139 - - c_height_total)), - position=(30, 80)) + ba.scrollwidget( + edit=self._scrollwidget, + size=( + self._width - 50, + max(100, self._height - 139 - c_height_total), + ), + position=(30, 80), + ) - def popup_menu_selected_choice(self, popup_window: popup.PopupMenuWindow, - choice: str) -> None: + def popup_menu_selected_choice( + self, popup_window: popup.PopupMenuWindow, choice: str + ) -> None: """Called when a choice is selected in the popup.""" del popup_window # unused if self._popup_type == 'partyMemberPress': @@ -352,22 +432,25 @@ class PartyWindow(ba.Window): ba.playsound(ba.getsound('error')) ba.screenmessage( ba.Lstr(resource='internal.cantKickHostError'), - color=(1, 0, 0)) + color=(1, 0, 0), + ) else: assert self._popup_party_member_client_id is not None # Ban for 5 minutes. result = ba.internal.disconnect_client( - self._popup_party_member_client_id, ban_time=5 * 60) + self._popup_party_member_client_id, ban_time=5 * 60 + ) if not result: ba.playsound(ba.getsound('error')) ba.screenmessage( ba.Lstr(resource='getTicketsWindow.unavailableText'), - color=(1, 0, 0)) + color=(1, 0, 0), + ) elif self._popup_type == 'menu': if choice in ('mute', 'unmute'): cfg = ba.app.config - cfg['Chat Muted'] = (choice == 'mute') + cfg['Chat Muted'] = choice == 'mute' cfg.apply_and_commit() self._update() else: @@ -376,33 +459,43 @@ class PartyWindow(ba.Window): def popup_menu_closing(self, popup_window: popup.PopupWindow) -> None: """Called when the popup is closing.""" - def _on_party_member_press(self, client_id: int, is_host: bool, - widget: ba.Widget) -> None: + def _on_party_member_press( + self, client_id: int, is_host: bool, widget: ba.Widget + ) -> None: # if we're the host, pop up 'kick' options for all non-host members if ba.internal.get_foreground_host_session() is not None: kick_str = ba.Lstr(resource='kickText') else: # kick-votes appeared in build 14248 - if (ba.internal.get_connection_to_host_info().get( - 'build_number', 0) < 14248): + if ( + ba.internal.get_connection_to_host_info().get('build_number', 0) + < 14248 + ): return kick_str = ba.Lstr(resource='kickVoteText') uiscale = ba.app.ui.uiscale popup.PopupMenuWindow( position=widget.get_screen_space_center(), - scale=(2.3 if uiscale is ba.UIScale.SMALL else - 1.65 if uiscale is ba.UIScale.MEDIUM else 1.23), + scale=( + 2.3 + if uiscale is ba.UIScale.SMALL + else 1.65 + if uiscale is ba.UIScale.MEDIUM + else 1.23 + ), choices=['kick'], choices_display=[kick_str], current_choice='kick', - delegate=self) + delegate=self, + ) self._popup_type = 'partyMemberPress' self._popup_party_member_client_id = client_id self._popup_party_member_is_host = is_host def _send_chat_message(self) -> None: ba.internal.chatmessage( - cast(str, ba.textwidget(query=self._text_field))) + cast(str, ba.textwidget(query=self._text_field)) + ) ba.textwidget(edit=self._text_field, text='') def close(self) -> None: diff --git a/dist/ba_data/python/bastd/ui/partyqueue.py b/dist/ba_data/python/bastd/ui/partyqueue.py index 7867322..f5355da 100644 --- a/dist/ba_data/python/bastd/ui/partyqueue.py +++ b/dist/ba_data/python/bastd/ui/partyqueue.py @@ -24,9 +24,15 @@ class PartyQueueWindow(ba.Window): class Dude: """Represents a single dude waiting in a server line.""" - def __init__(self, parent: PartyQueueWindow, distance: float, - initial_offset: float, is_player: bool, account_id: str, - name: str): + def __init__( + self, + parent: PartyQueueWindow, + distance: float, + initial_offset: float, + is_player: bool, + account_id: str, + name: str, + ): self.claimed = False self._line_left = parent.get_line_left() self._line_width = parent.get_line_width() @@ -38,13 +44,20 @@ class PartyQueueWindow(ba.Window): self._sc = sc = 1.1 if is_player else 0.6 + random.random() * 0.2 self._y_offs = -30.0 if is_player else -47.0 * sc self._last_boost_time = 0.0 - self._color = (0.2, 1.0, - 0.2) if is_player else (0.5 + 0.3 * random.random(), - 0.4 + 0.2 * random.random(), - 0.5 + 0.3 * random.random()) - self._eye_color = (0.7 * 1.0 + 0.3 * self._color[0], - 0.7 * 1.0 + 0.3 * self._color[1], - 0.7 * 1.0 + 0.3 * self._color[2]) + self._color = ( + (0.2, 1.0, 0.2) + if is_player + else ( + 0.5 + 0.3 * random.random(), + 0.4 + 0.2 * random.random(), + 0.5 + 0.3 * random.random(), + ) + ) + self._eye_color = ( + 0.7 * 1.0 + 0.3 * self._color[0], + 0.7 * 1.0 + 0.3 * self._color[1], + 0.7 * 1.0 + 0.3 * self._color[2], + ) self._body_image = ba.buttonwidget( parent=parent.get_root_widget(), selectable=True, @@ -52,28 +65,34 @@ class PartyQueueWindow(ba.Window): size=(sc * 60, sc * 80), color=self._color, texture=parent.lineup_tex, - model_transparent=parent.lineup_1_transparent_model) - ba.buttonwidget(edit=self._body_image, - on_activate_call=ba.WeakCall( - parent.on_account_press, account_id, - self._body_image)) + model_transparent=parent.lineup_1_transparent_model, + ) + ba.buttonwidget( + edit=self._body_image, + on_activate_call=ba.WeakCall( + parent.on_account_press, account_id, self._body_image + ), + ) ba.widget(edit=self._body_image, autoselect=True) self._eyes_image = ba.imagewidget( parent=parent.get_root_widget(), size=(sc * 36, sc * 18), texture=parent.lineup_tex, color=self._eye_color, - model_transparent=parent.eyes_model) - self._name_text = ba.textwidget(parent=parent.get_root_widget(), - size=(0, 0), - shadow=0, - flatness=1.0, - text=name, - maxwidth=100, - h_align='center', - v_align='center', - scale=0.75, - color=(1, 1, 1, 0.6)) + model_transparent=parent.eyes_model, + ) + self._name_text = ba.textwidget( + parent=parent.get_root_widget(), + size=(0, 0), + shadow=0, + flatness=1.0, + text=name, + maxwidth=100, + h_align='center', + v_align='center', + scale=0.75, + color=(1, 1, 1, 0.6), + ) self._update_image() # DEBUG: vis target pos.. @@ -85,13 +104,15 @@ class PartyQueueWindow(ba.Window): size=(sc * 60, sc * 80), color=self._color, texture=parent.lineup_tex, - model_transparent=parent.lineup_1_transparent_model) + model_transparent=parent.lineup_1_transparent_model, + ) self._eyes_image_target = ba.imagewidget( parent=parent.get_root_widget(), size=(sc * 36, sc * 18), texture=parent.lineup_tex, color=self._eye_color, - model_transparent=parent.eyes_model) + model_transparent=parent.eyes_model, + ) # (updates our image positions) self.set_target_distance(self._target_distance) else: @@ -111,54 +132,87 @@ class PartyQueueWindow(ba.Window): widget.delete() ba.pushcall( - ba.Call(kill_widgets, [ - self._body_image, self._eyes_image, - self._body_image_target, self._eyes_image_target, - self._name_text - ])) + ba.Call( + kill_widgets, + [ + self._body_image, + self._eyes_image, + self._body_image_target, + self._eyes_image_target, + self._name_text, + ], + ) + ) def set_target_distance(self, dist: float) -> None: """Set distance for a dude.""" self._target_distance = dist if self._debug: sc = self._sc - position = (self._line_left + self._line_width * - (1.0 - self._target_distance), - self._line_bottom - 30) - ba.imagewidget(edit=self._body_image_target, - position=(position[0] - sc * 30, - position[1] - sc * 25 - 70)) - ba.imagewidget(edit=self._eyes_image_target, - position=(position[0] - sc * 18, - position[1] + sc * 31 - 70)) + position = ( + self._line_left + + self._line_width * (1.0 - self._target_distance), + self._line_bottom - 30, + ) + ba.imagewidget( + edit=self._body_image_target, + position=( + position[0] - sc * 30, + position[1] - sc * 25 - 70, + ), + ) + ba.imagewidget( + edit=self._eyes_image_target, + position=( + position[0] - sc * 18, + position[1] + sc * 31 - 70, + ), + ) def step(self, smoothing: float) -> None: """Step this dude.""" - self._distance = (smoothing * self._distance + - (1.0 - smoothing) * self._target_distance) + self._distance = ( + smoothing * self._distance + + (1.0 - smoothing) * self._target_distance + ) self._update_image() self._boost_brightness *= 0.9 def _update_image(self) -> None: sc = self._sc - position = (self._line_left + self._line_width * - (1.0 - self._distance), self._line_bottom + 40) + position = ( + self._line_left + self._line_width * (1.0 - self._distance), + self._line_bottom + 40, + ) brightness = 1.0 + self._boost_brightness - ba.buttonwidget(edit=self._body_image, - position=(position[0] - sc * 30, - position[1] - sc * 25 + self._y_offs), - color=(self._color[0] * brightness, - self._color[1] * brightness, - self._color[2] * brightness)) - ba.imagewidget(edit=self._eyes_image, - position=(position[0] - sc * 18, - position[1] + sc * 31 + self._y_offs), - color=(self._eye_color[0] * brightness, - self._eye_color[1] * brightness, - self._eye_color[2] * brightness)) - ba.textwidget(edit=self._name_text, - position=(position[0] - sc * 0, - position[1] + sc * 40.0)) + ba.buttonwidget( + edit=self._body_image, + position=( + position[0] - sc * 30, + position[1] - sc * 25 + self._y_offs, + ), + color=( + self._color[0] * brightness, + self._color[1] * brightness, + self._color[2] * brightness, + ), + ) + ba.imagewidget( + edit=self._eyes_image, + position=( + position[0] - sc * 18, + position[1] + sc * 31 + self._y_offs, + ), + color=( + self._eye_color[0] * brightness, + self._eye_color[1] * brightness, + self._eye_color[2] * brightness, + ), + ) + ba.textwidget( + edit=self._name_text, + position=(position[0] - sc * 0, position[1] + sc * 40.0), + ) def boost(self, amount: float, smoothing: float) -> None: """Boost this dude.""" @@ -192,39 +246,55 @@ class PartyQueueWindow(ba.Window): self._boost_tickets = 0 self._boost_strength = 0.0 self._angry_computer_transparent_model = ba.getmodel( - 'angryComputerTransparent') + 'angryComputerTransparent' + ) self._angry_computer_image: ba.Widget | None = None self.lineup_1_transparent_model = ba.getmodel( - 'playerLineup1Transparent') + 'playerLineup1Transparent' + ) self._lineup_2_transparent_model = ba.getmodel( - 'playerLineup2Transparent') + 'playerLineup2Transparent' + ) self._lineup_3_transparent_model = ba.getmodel( - 'playerLineup3Transparent') + 'playerLineup3Transparent' + ) self._lineup_4_transparent_model = ba.getmodel( - 'playerLineup4Transparent') + 'playerLineup4Transparent' + ) self._line_image: ba.Widget | None = None self.eyes_model = ba.getmodel('plasticEyesTransparent') self._white_tex = ba.gettexture('white') uiscale = ba.app.ui.uiscale - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height), - color=(0.45, 0.63, 0.15), - transition='in_scale', - scale=(1.4 if uiscale is ba.UIScale.SMALL else - 1.2 if uiscale is ba.UIScale.MEDIUM else 1.0))) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height), + color=(0.45, 0.63, 0.15), + transition='in_scale', + scale=( + 1.4 + if uiscale is ba.UIScale.SMALL + else 1.2 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + ) + ) - self._cancel_button = ba.buttonwidget(parent=self._root_widget, - scale=1.0, - position=(60, self._height - 80), - size=(50, 50), - label='', - on_activate_call=self.close, - autoselect=True, - color=(0.45, 0.63, 0.15), - icon=ba.gettexture('crossOut'), - iconscale=1.2) - ba.containerwidget(edit=self._root_widget, - cancel_button=self._cancel_button) + self._cancel_button = ba.buttonwidget( + parent=self._root_widget, + scale=1.0, + position=(60, self._height - 80), + size=(50, 50), + label='', + on_activate_call=self.close, + autoselect=True, + color=(0.45, 0.63, 0.15), + icon=ba.gettexture('crossOut'), + iconscale=1.2, + ) + ba.containerwidget( + edit=self._root_widget, cancel_button=self._cancel_button + ) self._title_text = ba.textwidget( parent=self._root_widget, @@ -235,32 +305,35 @@ class PartyQueueWindow(ba.Window): h_align='center', v_align='center', text=ba.Lstr(resource='internal.connectingToPartyText'), - maxwidth=self._width * 0.65) + maxwidth=self._width * 0.65, + ) - self._tickets_text = ba.textwidget(parent=self._root_widget, - position=(self._width - 180, - self._height - 20), - size=(0, 0), - color=(0.2, 1.0, 0.2), - scale=0.7, - h_align='center', - v_align='center', - text='') + self._tickets_text = ba.textwidget( + parent=self._root_widget, + position=(self._width - 180, self._height - 20), + size=(0, 0), + color=(0.2, 1.0, 0.2), + scale=0.7, + h_align='center', + v_align='center', + text='', + ) # update at roughly 30fps - self._update_timer = ba.Timer(0.033, - ba.WeakCall(self.update), - repeat=True, - timetype=ba.TimeType.REAL) + self._update_timer = ba.Timer( + 0.033, + ba.WeakCall(self.update), + repeat=True, + timetype=ba.TimeType.REAL, + ) self.update() def __del__(self) -> None: try: ba.app.ui.have_party_queue_window = False - ba.internal.add_transaction({ - 'type': 'PARTY_QUEUE_REMOVE', - 'q': self._queue_id - }) + ba.internal.add_transaction( + {'type': 'PARTY_QUEUE_REMOVE', 'q': self._queue_id} + ) ba.internal.run_transactions() except Exception: ba.print_exception('Error removing self from party queue.') @@ -277,16 +350,19 @@ class PartyQueueWindow(ba.Window): """(internal)""" return self._line_bottom - def on_account_press(self, account_id: str | None, - origin_widget: ba.Widget) -> None: + def on_account_press( + self, account_id: str | None, origin_widget: ba.Widget + ) -> None: """A dude was clicked so we should show his account info.""" from bastd.ui.account import viewer + if account_id is None: ba.playsound(ba.getsound('error')) return viewer.AccountViewerWindow( account_id=account_id, - position=origin_widget.get_screen_space_center()) + position=origin_widget.get_screen_space_center(), + ) def close(self) -> None: """Close the ui.""" @@ -299,7 +375,8 @@ class PartyQueueWindow(ba.Window): position=(self._width - 180, self._height * 0.5 - 65), size=(150, 150), texture=self.lineup_tex, - model_transparent=self._angry_computer_transparent_model) + model_transparent=self._angry_computer_transparent_model, + ) if self._line_image is None: self._line_image = ba.imagewidget( parent=self._root_widget, @@ -307,7 +384,8 @@ class PartyQueueWindow(ba.Window): opacity=0.2, position=(self._line_left, self._line_bottom - 2.0), size=(self._line_width, 4.0), - texture=self._white_tex) + texture=self._white_tex, + ) # now go through the data they sent, creating dudes for us and our # enemies as needed and updating target positions on all of them.. @@ -319,10 +397,15 @@ class PartyQueueWindow(ba.Window): # always have a dude for ourself.. if -1 not in self._dudes_by_id: dude = self.Dude( - self, response['d'], self._initial_offset, True, + self, + response['d'], + self._initial_offset, + True, ba.internal.get_v1_account_misc_read_val_2( - 'resolvedAccountID', None), - ba.internal.get_v1_account_display_string()) + 'resolvedAccountID', None + ), + ba.internal.get_v1_account_display_string(), + ) self._dudes_by_id[-1] = dude self._dudes.append(dude) else: @@ -330,11 +413,21 @@ class PartyQueueWindow(ba.Window): self._dudes_by_id[-1].claimed = True # now create/destroy enemies - for (enemy_id, enemy_distance, enemy_account_id, - enemy_name) in response['e']: + for ( + enemy_id, + enemy_distance, + enemy_account_id, + enemy_name, + ) in response['e']: if enemy_id not in self._dudes_by_id: - dude = self.Dude(self, enemy_distance, self._initial_offset, - False, enemy_account_id, enemy_name) + dude = self.Dude( + self, + enemy_distance, + self._initial_offset, + False, + enemy_account_id, + enemy_name, + ) self._dudes_by_id[enemy_id] = dude self._dudes.append(dude) else: @@ -343,9 +436,13 @@ class PartyQueueWindow(ba.Window): # remove unclaimed dudes from both of our lists # noinspection PyUnresolvedReferences - self._dudes_by_id = dict([ - item for item in list(self._dudes_by_id.items()) if item[1].claimed - ]) + self._dudes_by_id = dict( + [ + item + for item in list(self._dudes_by_id.items()) + if item[1].claimed + ] + ) self._dudes = [dude for dude in self._dudes if dude.claimed] def _hide_field(self) -> None: @@ -370,23 +467,25 @@ class PartyQueueWindow(ba.Window): return if response is not None: - should_show_field = (response.get('d') is not None) + should_show_field = response.get('d') is not None self._smoothing = response['s'] self._initial_offset = response['o'] # If they gave us a position, show the field. if should_show_field: - ba.textwidget(edit=self._title_text, - text=ba.Lstr(resource='waitingInLineText'), - position=(self._width * 0.5, - self._height * 0.85)) + ba.textwidget( + edit=self._title_text, + text=ba.Lstr(resource='waitingInLineText'), + position=(self._width * 0.5, self._height * 0.85), + ) self._update_field(response) self._field_shown = True if not should_show_field and self._field_shown: ba.textwidget( edit=self._title_text, text=ba.Lstr(resource='internal.connectingToPartyText'), - position=(self._width * 0.5, self._height * 0.55)) + position=(self._width * 0.5, self._height * 0.55), + ) self._hide_field() self._field_shown = False @@ -405,7 +504,8 @@ class PartyQueueWindow(ba.Window): on_activate_call=self.on_boost_press, enable_sound=False, color=(0, 1, 0), - autoselect=True) + autoselect=True, + ) self._boost_label = ba.textwidget( parent=self._root_widget, draw_controller=self._boost_button, @@ -416,7 +516,8 @@ class PartyQueueWindow(ba.Window): h_align='center', v_align='center', text=ba.Lstr(resource='boostText'), - maxwidth=150) + maxwidth=150, + ) self._boost_price = ba.textwidget( parent=self._root_widget, draw_controller=self._boost_button, @@ -426,9 +527,10 @@ class PartyQueueWindow(ba.Window): scale=0.9, h_align='center', v_align='center', - text=ba.charstr(ba.SpecialChar.TICKET) + - str(self._boost_tickets), - maxwidth=150) + text=ba.charstr(ba.SpecialChar.TICKET) + + str(self._boost_tickets), + maxwidth=150, + ) else: if self._boost_button is not None: self._boost_button.delete() @@ -447,17 +549,22 @@ class PartyQueueWindow(ba.Window): # enforce a delay between connection attempts # (in case they're jamming on the boost button) now = time.time() - if (self._last_connect_attempt_time is None - or now - self._last_connect_attempt_time > 10.0): - ba.internal.connect_to_party(address=self._address, - port=self._port, - print_progress=False) + if ( + self._last_connect_attempt_time is None + or now - self._last_connect_attempt_time > 10.0 + ): + ba.internal.connect_to_party( + address=self._address, + port=self._port, + print_progress=False, + ) self._last_connect_attempt_time = now def on_boost_press(self) -> None: """Boost was pressed.""" from bastd.ui import account from bastd.ui import getcurrency + if ba.internal.get_v1_account_state() != 'signed_in': account.show_sign_in_prompt() return @@ -472,9 +579,10 @@ class PartyQueueWindow(ba.Window): { 'type': 'PARTY_QUEUE_BOOST', 't': self._boost_tickets, - 'q': self._queue_id + 'q': self._queue_id, }, - callback=ba.WeakCall(self.on_update_response)) + callback=ba.WeakCall(self.on_update_response), + ) # lets not run these immediately (since they may be rapid-fire, # just bucket them until the next tick) @@ -492,24 +600,31 @@ class PartyQueueWindow(ba.Window): # Update boost-price. if self._boost_price is not None: - ba.textwidget(edit=self._boost_price, - text=ba.charstr(ba.SpecialChar.TICKET) + - str(self._boost_tickets)) + ba.textwidget( + edit=self._boost_price, + text=ba.charstr(ba.SpecialChar.TICKET) + + str(self._boost_tickets), + ) # Update boost button color based on if we have enough moola. if self._boost_button is not None: - can_boost = ((ba.internal.get_v1_account_state() == 'signed_in' - and ba.internal.get_v1_account_ticket_count() >= - self._boost_tickets)) - ba.buttonwidget(edit=self._boost_button, - color=(0, 1, 0) if can_boost else (0.7, 0.7, 0.7)) + can_boost = ( + ba.internal.get_v1_account_state() == 'signed_in' + and ba.internal.get_v1_account_ticket_count() + >= self._boost_tickets + ) + ba.buttonwidget( + edit=self._boost_button, + color=(0, 1, 0) if can_boost else (0.7, 0.7, 0.7), + ) # Update ticket-count. if self._tickets_text is not None: if self._boost_button is not None: if ba.internal.get_v1_account_state() == 'signed_in': val = ba.charstr(ba.SpecialChar.TICKET) + str( - ba.internal.get_v1_account_ticket_count()) + ba.internal.get_v1_account_ticket_count() + ) else: val = ba.charstr(ba.SpecialChar.TICKET) + '???' ba.textwidget(edit=self._tickets_text, text=val) @@ -517,16 +632,16 @@ class PartyQueueWindow(ba.Window): ba.textwidget(edit=self._tickets_text, text='') current_time = ba.time(ba.TimeType.REAL) - if (self._last_transaction_time is None - or current_time - self._last_transaction_time > 0.001 * - ba.internal.get_v1_account_misc_read_val('pqInt', 5000)): + if ( + self._last_transaction_time is None + or current_time - self._last_transaction_time + > 0.001 * ba.internal.get_v1_account_misc_read_val('pqInt', 5000) + ): self._last_transaction_time = current_time ba.internal.add_transaction( - { - 'type': 'PARTY_QUEUE_QUERY', - 'q': self._queue_id - }, - callback=ba.WeakCall(self.on_update_response)) + {'type': 'PARTY_QUEUE_QUERY', 'q': self._queue_id}, + callback=ba.WeakCall(self.on_update_response), + ) ba.internal.run_transactions() # step our dudes diff --git a/dist/ba_data/python/bastd/ui/play.py b/dist/ba_data/python/bastd/ui/play.py index 498c0cc..976109f 100644 --- a/dist/ba_data/python/bastd/ui/play.py +++ b/dist/ba_data/python/bastd/ui/play.py @@ -16,9 +16,11 @@ if TYPE_CHECKING: class PlayWindow(ba.Window): """Window for selecting overall play type.""" - def __init__(self, - transition: str = 'in_right', - origin_widget: ba.Widget | None = None): + def __init__( + self, + transition: str = 'in_right', + origin_widget: ba.Widget | None = None, + ): # pylint: disable=too-many-statements # pylint: disable=too-many-locals import threading @@ -48,14 +50,22 @@ class PlayWindow(ba.Window): self._r = 'playWindow' - super().__init__(root_widget=ba.containerwidget( - size=(width, height), - transition=transition, - toolbar_visibility='menu_full', - scale_origin_stack_offset=scale_origin, - scale=(1.6 if uiscale is ba.UIScale.SMALL else - 0.9 if uiscale is ba.UIScale.MEDIUM else 0.8), - stack_offset=(0, 0) if uiscale is ba.UIScale.SMALL else (0, 0))) + super().__init__( + root_widget=ba.containerwidget( + size=(width, height), + transition=transition, + toolbar_visibility='menu_full', + scale_origin_stack_offset=scale_origin, + scale=( + 1.6 + if uiscale is ba.UIScale.SMALL + else 0.9 + if uiscale is ba.UIScale.MEDIUM + else 0.8 + ), + stack_offset=(0, 0) if uiscale is ba.UIScale.SMALL else (0, 0), + ) + ) self._back_button = back_button = btn = ba.buttonwidget( parent=self._root_widget, position=(55 + x_offs, height - 132), @@ -65,7 +75,8 @@ class PlayWindow(ba.Window): text_scale=1.2, autoselect=True, label=ba.Lstr(resource='backText'), - button_type='back') + button_type='back', + ) txt = ba.textwidget( parent=self._root_widget, @@ -73,20 +84,25 @@ class PlayWindow(ba.Window): # position=(width * 0.5, height - # (101 if main_menu else 61)), size=(0, 0), - text=ba.Lstr(resource=( - self._r + - '.titleText') if self._is_main_menu else 'playlistsText'), + text=ba.Lstr( + resource=(self._r + '.titleText') + if self._is_main_menu + else 'playlistsText' + ), scale=1.7, res_scale=2.0, maxwidth=400, color=ba.app.ui.heading_color, h_align='center', - v_align='center') + v_align='center', + ) - ba.buttonwidget(edit=btn, - button_type='backSmall', - size=(60, 60), - label=ba.charstr(ba.SpecialChar.BACK)) + ba.buttonwidget( + edit=btn, + button_type='backSmall', + size=(60, 60), + label=ba.charstr(ba.SpecialChar.BACK), + ) if ba.app.ui.use_toolbars and uiscale is ba.UIScale.SMALL: ba.textwidget(edit=txt, text='') @@ -94,22 +110,26 @@ class PlayWindow(ba.Window): v -= 100 clr = (0.6, 0.7, 0.6, 1.0) v -= 280 if self._is_main_menu else 180 - v += (30 - if ba.app.ui.use_toolbars and uiscale is ba.UIScale.SMALL else 0) + v += 30 if ba.app.ui.use_toolbars and uiscale is ba.UIScale.SMALL else 0 hoffs = x_offs + 80 if self._is_main_menu else x_offs - 100 scl = 1.13 if self._is_main_menu else 0.68 self._lineup_tex = ba.gettexture('playerLineup') angry_computer_transparent_model = ba.getmodel( - 'angryComputerTransparent') + 'angryComputerTransparent' + ) self._lineup_1_transparent_model = ba.getmodel( - 'playerLineup1Transparent') + 'playerLineup1Transparent' + ) self._lineup_2_transparent_model = ba.getmodel( - 'playerLineup2Transparent') + 'playerLineup2Transparent' + ) self._lineup_3_transparent_model = ba.getmodel( - 'playerLineup3Transparent') + 'playerLineup3Transparent' + ) self._lineup_4_transparent_model = ba.getmodel( - 'playerLineup4Transparent') + 'playerLineup4Transparent' + ) self._eyes_model = ba.getmodel('plasticEyesTransparent') self._coop_button: ba.Widget | None = None @@ -119,87 +139,103 @@ class PlayWindow(ba.Window): self._coop_button = btn = ba.buttonwidget( parent=self._root_widget, position=(hoffs, v + (scl * 15 if self._is_main_menu else 0)), - size=(scl * button_width, - scl * (300 if self._is_main_menu else 360)), + size=( + scl * button_width, + scl * (300 if self._is_main_menu else 360), + ), extra_touch_border_scale=0.1, autoselect=True, label='', button_type='square', text_scale=1.13, - on_activate_call=self._coop) + on_activate_call=self._coop, + ) if ba.app.ui.use_toolbars and uiscale is ba.UIScale.SMALL: ba.widget( edit=btn, - left_widget=ba.internal.get_special_widget('back_button')) + left_widget=ba.internal.get_special_widget('back_button'), + ) ba.widget( edit=btn, - up_widget=ba.internal.get_special_widget('account_button')) - ba.widget(edit=btn, - down_widget=ba.internal.get_special_widget( - 'settings_button')) + up_widget=ba.internal.get_special_widget('account_button'), + ) + ba.widget( + edit=btn, + down_widget=ba.internal.get_special_widget( + 'settings_button' + ), + ) - self._draw_dude(0, - btn, - hoffs, - v, - scl, - position=(140, 30), - color=(0.72, 0.4, 1.0)) - self._draw_dude(1, - btn, - hoffs, - v, - scl, - position=(185, 53), - color=(0.71, 0.5, 1.0)) - self._draw_dude(2, - btn, - hoffs, - v, - scl, - position=(220, 27), - color=(0.67, 0.44, 1.0)) - self._draw_dude(3, - btn, - hoffs, - v, - scl, - position=(255, 57), - color=(0.7, 0.3, 1.0)) - ba.imagewidget(parent=self._root_widget, - draw_controller=btn, - position=(hoffs + scl * 230, v + scl * 153), - size=(scl * 115, scl * 115), - texture=self._lineup_tex, - model_transparent=angry_computer_transparent_model) + self._draw_dude( + 0, + btn, + hoffs, + v, + scl, + position=(140, 30), + color=(0.72, 0.4, 1.0), + ) + self._draw_dude( + 1, + btn, + hoffs, + v, + scl, + position=(185, 53), + color=(0.71, 0.5, 1.0), + ) + self._draw_dude( + 2, + btn, + hoffs, + v, + scl, + position=(220, 27), + color=(0.67, 0.44, 1.0), + ) + self._draw_dude( + 3, btn, hoffs, v, scl, position=(255, 57), color=(0.7, 0.3, 1.0) + ) + ba.imagewidget( + parent=self._root_widget, + draw_controller=btn, + position=(hoffs + scl * 230, v + scl * 153), + size=(scl * 115, scl * 115), + texture=self._lineup_tex, + model_transparent=angry_computer_transparent_model, + ) - ba.textwidget(parent=self._root_widget, - draw_controller=btn, - position=(hoffs + scl * (-10), v + scl * 95), - size=(scl * button_width, scl * 50), - text=ba.Lstr( - resource='playModes.singlePlayerCoopText', - fallback_resource='playModes.coopText'), - maxwidth=scl * button_width * 0.7, - res_scale=1.5, - h_align='center', - v_align='center', - color=(0.7, 0.9, 0.7, 1.0), - scale=scl * 2.3) + ba.textwidget( + parent=self._root_widget, + draw_controller=btn, + position=(hoffs + scl * (-10), v + scl * 95), + size=(scl * button_width, scl * 50), + text=ba.Lstr( + resource='playModes.singlePlayerCoopText', + fallback_resource='playModes.coopText', + ), + maxwidth=scl * button_width * 0.7, + res_scale=1.5, + h_align='center', + v_align='center', + color=(0.7, 0.9, 0.7, 1.0), + scale=scl * 2.3, + ) - ba.textwidget(parent=self._root_widget, - draw_controller=btn, - position=(hoffs + scl * (-10), v + (scl * 54)), - size=(scl * button_width, scl * 30), - text=ba.Lstr(resource=self._r + - '.oneToFourPlayersText'), - h_align='center', - v_align='center', - scale=0.83 * scl, - flatness=1.0, - maxwidth=scl * button_width * 0.7, - color=clr) + ba.textwidget( + parent=self._root_widget, + draw_controller=btn, + position=(hoffs + scl * (-10), v + (scl * 54)), + size=(scl * button_width, scl * 30), + text=ba.Lstr(resource=self._r + '.oneToFourPlayersText'), + h_align='center', + v_align='center', + scale=0.83 * scl, + flatness=1.0, + maxwidth=scl * button_width * 0.7, + color=clr, + ) scl = 0.5 if self._is_main_menu else 0.68 hoffs += 440 if self._is_main_menu else 216 @@ -208,216 +244,269 @@ class PlayWindow(ba.Window): self._teams_button = btn = ba.buttonwidget( parent=self._root_widget, position=(hoffs, v + (scl * 15 if self._is_main_menu else 0)), - size=(scl * button_width, - scl * (300 if self._is_main_menu else 360)), + size=( + scl * button_width, + scl * (300 if self._is_main_menu else 360), + ), extra_touch_border_scale=0.1, autoselect=True, label='', button_type='square', text_scale=1.13, - on_activate_call=self._team_tourney) + on_activate_call=self._team_tourney, + ) if ba.app.ui.use_toolbars: ba.widget( edit=btn, - up_widget=ba.internal.get_special_widget( - 'tickets_plus_button'), - right_widget=ba.internal.get_special_widget('party_button')) + up_widget=ba.internal.get_special_widget('tickets_plus_button'), + right_widget=ba.internal.get_special_widget('party_button'), + ) xxx = -14 - self._draw_dude(2, - btn, - hoffs, - v, - scl, - position=(xxx + 148, 30), - color=(0.2, 0.4, 1.0)) - self._draw_dude(3, - btn, - hoffs, - v, - scl, - position=(xxx + 181, 53), - color=(0.3, 0.4, 1.0)) - self._draw_dude(1, - btn, - hoffs, - v, - scl, - position=(xxx + 216, 33), - color=(0.3, 0.5, 1.0)) - self._draw_dude(0, - btn, - hoffs, - v, - scl, - position=(xxx + 245, 57), - color=(0.3, 0.5, 1.0)) + self._draw_dude( + 2, + btn, + hoffs, + v, + scl, + position=(xxx + 148, 30), + color=(0.2, 0.4, 1.0), + ) + self._draw_dude( + 3, + btn, + hoffs, + v, + scl, + position=(xxx + 181, 53), + color=(0.3, 0.4, 1.0), + ) + self._draw_dude( + 1, + btn, + hoffs, + v, + scl, + position=(xxx + 216, 33), + color=(0.3, 0.5, 1.0), + ) + self._draw_dude( + 0, + btn, + hoffs, + v, + scl, + position=(xxx + 245, 57), + color=(0.3, 0.5, 1.0), + ) xxx = 155 - self._draw_dude(0, - btn, - hoffs, - v, - scl, - position=(xxx + 151, 30), - color=(1.0, 0.5, 0.4)) - self._draw_dude(1, - btn, - hoffs, - v, - scl, - position=(xxx + 189, 53), - color=(1.0, 0.58, 0.58)) - self._draw_dude(3, - btn, - hoffs, - v, - scl, - position=(xxx + 223, 27), - color=(1.0, 0.5, 0.5)) - self._draw_dude(2, - btn, - hoffs, - v, - scl, - position=(xxx + 257, 57), - color=(1.0, 0.5, 0.5)) + self._draw_dude( + 0, + btn, + hoffs, + v, + scl, + position=(xxx + 151, 30), + color=(1.0, 0.5, 0.4), + ) + self._draw_dude( + 1, + btn, + hoffs, + v, + scl, + position=(xxx + 189, 53), + color=(1.0, 0.58, 0.58), + ) + self._draw_dude( + 3, + btn, + hoffs, + v, + scl, + position=(xxx + 223, 27), + color=(1.0, 0.5, 0.5), + ) + self._draw_dude( + 2, + btn, + hoffs, + v, + scl, + position=(xxx + 257, 57), + color=(1.0, 0.5, 0.5), + ) - ba.textwidget(parent=self._root_widget, - draw_controller=btn, - position=(hoffs + scl * (-10), v + scl * 95), - size=(scl * button_width, scl * 50), - text=ba.Lstr(resource='playModes.teamsText', - fallback_resource='teamsText'), - res_scale=1.5, - maxwidth=scl * button_width * 0.7, - h_align='center', - v_align='center', - color=(0.7, 0.9, 0.7, 1.0), - scale=scl * 2.3) - ba.textwidget(parent=self._root_widget, - draw_controller=btn, - position=(hoffs + scl * (-10), v + (scl * 54)), - size=(scl * button_width, scl * 30), - text=ba.Lstr(resource=self._r + - '.twoToEightPlayersText'), - h_align='center', - v_align='center', - res_scale=1.5, - scale=0.9 * scl, - flatness=1.0, - maxwidth=scl * button_width * 0.7, - color=clr) + ba.textwidget( + parent=self._root_widget, + draw_controller=btn, + position=(hoffs + scl * (-10), v + scl * 95), + size=(scl * button_width, scl * 50), + text=ba.Lstr( + resource='playModes.teamsText', fallback_resource='teamsText' + ), + res_scale=1.5, + maxwidth=scl * button_width * 0.7, + h_align='center', + v_align='center', + color=(0.7, 0.9, 0.7, 1.0), + scale=scl * 2.3, + ) + ba.textwidget( + parent=self._root_widget, + draw_controller=btn, + position=(hoffs + scl * (-10), v + (scl * 54)), + size=(scl * button_width, scl * 30), + text=ba.Lstr(resource=self._r + '.twoToEightPlayersText'), + h_align='center', + v_align='center', + res_scale=1.5, + scale=0.9 * scl, + flatness=1.0, + maxwidth=scl * button_width * 0.7, + color=clr, + ) hoffs += 0 if self._is_main_menu else 300 v -= 155 if self._is_main_menu else 0 self._free_for_all_button = btn = ba.buttonwidget( parent=self._root_widget, position=(hoffs, v + (scl * 15 if self._is_main_menu else 0)), - size=(scl * button_width, - scl * (300 if self._is_main_menu else 360)), + size=( + scl * button_width, + scl * (300 if self._is_main_menu else 360), + ), extra_touch_border_scale=0.1, autoselect=True, label='', button_type='square', text_scale=1.13, - on_activate_call=self._free_for_all) + on_activate_call=self._free_for_all, + ) xxx = -5 - self._draw_dude(0, - btn, - hoffs, - v, - scl, - position=(xxx + 140, 30), - color=(0.4, 1.0, 0.4)) - self._draw_dude(3, - btn, - hoffs, - v, - scl, - position=(xxx + 185, 53), - color=(1.0, 0.4, 0.5)) - self._draw_dude(1, - btn, - hoffs, - v, - scl, - position=(xxx + 220, 27), - color=(0.4, 0.5, 1.0)) - self._draw_dude(2, - btn, - hoffs, - v, - scl, - position=(xxx + 255, 57), - color=(0.5, 1.0, 0.4)) + self._draw_dude( + 0, + btn, + hoffs, + v, + scl, + position=(xxx + 140, 30), + color=(0.4, 1.0, 0.4), + ) + self._draw_dude( + 3, + btn, + hoffs, + v, + scl, + position=(xxx + 185, 53), + color=(1.0, 0.4, 0.5), + ) + self._draw_dude( + 1, + btn, + hoffs, + v, + scl, + position=(xxx + 220, 27), + color=(0.4, 0.5, 1.0), + ) + self._draw_dude( + 2, + btn, + hoffs, + v, + scl, + position=(xxx + 255, 57), + color=(0.5, 1.0, 0.4), + ) xxx = 140 - self._draw_dude(2, - btn, - hoffs, - v, - scl, - position=(xxx + 148, 30), - color=(1.0, 0.9, 0.4)) - self._draw_dude(0, - btn, - hoffs, - v, - scl, - position=(xxx + 182, 53), - color=(0.7, 1.0, 0.5)) - self._draw_dude(3, - btn, - hoffs, - v, - scl, - position=(xxx + 233, 27), - color=(0.7, 0.5, 0.9)) - self._draw_dude(1, - btn, - hoffs, - v, - scl, - position=(xxx + 266, 53), - color=(0.4, 0.5, 0.8)) - ba.textwidget(parent=self._root_widget, - draw_controller=btn, - position=(hoffs + scl * (-10), v + scl * 95), - size=(scl * button_width, scl * 50), - text=ba.Lstr(resource='playModes.freeForAllText', - fallback_resource='freeForAllText'), - maxwidth=scl * button_width * 0.7, - h_align='center', - v_align='center', - color=(0.7, 0.9, 0.7, 1.0), - scale=scl * 1.9) - ba.textwidget(parent=self._root_widget, - draw_controller=btn, - position=(hoffs + scl * (-10), v + (scl * 54)), - size=(scl * button_width, scl * 30), - text=ba.Lstr(resource=self._r + - '.twoToEightPlayersText'), - h_align='center', - v_align='center', - scale=0.9 * scl, - flatness=1.0, - maxwidth=scl * button_width * 0.7, - color=clr) + self._draw_dude( + 2, + btn, + hoffs, + v, + scl, + position=(xxx + 148, 30), + color=(1.0, 0.9, 0.4), + ) + self._draw_dude( + 0, + btn, + hoffs, + v, + scl, + position=(xxx + 182, 53), + color=(0.7, 1.0, 0.5), + ) + self._draw_dude( + 3, + btn, + hoffs, + v, + scl, + position=(xxx + 233, 27), + color=(0.7, 0.5, 0.9), + ) + self._draw_dude( + 1, + btn, + hoffs, + v, + scl, + position=(xxx + 266, 53), + color=(0.4, 0.5, 0.8), + ) + ba.textwidget( + parent=self._root_widget, + draw_controller=btn, + position=(hoffs + scl * (-10), v + scl * 95), + size=(scl * button_width, scl * 50), + text=ba.Lstr( + resource='playModes.freeForAllText', + fallback_resource='freeForAllText', + ), + maxwidth=scl * button_width * 0.7, + h_align='center', + v_align='center', + color=(0.7, 0.9, 0.7, 1.0), + scale=scl * 1.9, + ) + ba.textwidget( + parent=self._root_widget, + draw_controller=btn, + position=(hoffs + scl * (-10), v + (scl * 54)), + size=(scl * button_width, scl * 30), + text=ba.Lstr(resource=self._r + '.twoToEightPlayersText'), + h_align='center', + v_align='center', + scale=0.9 * scl, + flatness=1.0, + maxwidth=scl * button_width * 0.7, + color=clr, + ) if ba.app.ui.use_toolbars and uiscale is ba.UIScale.SMALL: back_button.delete() - ba.containerwidget(edit=self._root_widget, - on_cancel_call=self._back, - selected_child=self._coop_button - if self._is_main_menu else self._teams_button) + ba.containerwidget( + edit=self._root_widget, + on_cancel_call=self._back, + selected_child=self._coop_button + if self._is_main_menu + else self._teams_button, + ) else: ba.buttonwidget(edit=back_button, on_activate_call=self._back) - ba.containerwidget(edit=self._root_widget, - cancel_button=back_button, - selected_child=self._coop_button - if self._is_main_menu else self._teams_button) + ba.containerwidget( + edit=self._root_widget, + cancel_button=back_button, + selected_child=self._coop_button + if self._is_main_menu + else self._teams_button, + ) self._restore_state() @@ -434,128 +523,181 @@ class PlayWindow(ba.Window): # pylint: disable=cyclic-import if self._is_main_menu: from bastd.ui.mainmenu import MainMenuWindow + self._save_state() ba.app.ui.set_main_menu_window( - MainMenuWindow(transition='in_left').get_root_widget()) - ba.containerwidget(edit=self._root_widget, - transition=self._transition_out) + MainMenuWindow(transition='in_left').get_root_widget() + ) + ba.containerwidget( + edit=self._root_widget, transition=self._transition_out + ) else: from bastd.ui.gather import GatherWindow + self._save_state() ba.app.ui.set_main_menu_window( - GatherWindow(transition='in_left').get_root_widget()) - ba.containerwidget(edit=self._root_widget, - transition=self._transition_out) + GatherWindow(transition='in_left').get_root_widget() + ) + ba.containerwidget( + edit=self._root_widget, transition=self._transition_out + ) def _coop(self) -> None: # pylint: disable=cyclic-import from bastd.ui.account import show_sign_in_prompt from bastd.ui.coop.browser import CoopBrowserWindow + if ba.internal.get_v1_account_state() != 'signed_in': show_sign_in_prompt() return self._save_state() ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.ui.set_main_menu_window( - CoopBrowserWindow( - origin_widget=self._coop_button).get_root_widget()) + CoopBrowserWindow(origin_widget=self._coop_button).get_root_widget() + ) def _team_tourney(self) -> None: # pylint: disable=cyclic-import from bastd.ui.playlist.browser import PlaylistBrowserWindow + self._save_state() ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.ui.set_main_menu_window( PlaylistBrowserWindow( - origin_widget=self._teams_button, - sessiontype=ba.DualTeamSession).get_root_widget()) + origin_widget=self._teams_button, sessiontype=ba.DualTeamSession + ).get_root_widget() + ) def _free_for_all(self) -> None: # pylint: disable=cyclic-import from bastd.ui.playlist.browser import PlaylistBrowserWindow + self._save_state() ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.ui.set_main_menu_window( PlaylistBrowserWindow( origin_widget=self._free_for_all_button, - sessiontype=ba.FreeForAllSession).get_root_widget()) + sessiontype=ba.FreeForAllSession, + ).get_root_widget() + ) - def _draw_dude(self, i: int, btn: ba.Widget, hoffs: float, v: float, - scl: float, position: tuple[float, float], - color: tuple[float, float, float]) -> None: + def _draw_dude( + self, + i: int, + btn: ba.Widget, + hoffs: float, + v: float, + scl: float, + position: tuple[float, float], + color: tuple[float, float, float], + ) -> None: h_extra = -100 v_extra = 130 - eye_color = (0.7 * 1.0 + 0.3 * color[0], 0.7 * 1.0 + 0.3 * color[1], - 0.7 * 1.0 + 0.3 * color[2]) + eye_color = ( + 0.7 * 1.0 + 0.3 * color[0], + 0.7 * 1.0 + 0.3 * color[1], + 0.7 * 1.0 + 0.3 * color[2], + ) if i == 0: - ba.imagewidget(parent=self._root_widget, - draw_controller=btn, - position=(hoffs + scl * (h_extra + position[0]), - v + scl * (v_extra + position[1])), - size=(scl * 60, scl * 80), - color=color, - texture=self._lineup_tex, - model_transparent=self._lineup_1_transparent_model) ba.imagewidget( parent=self._root_widget, draw_controller=btn, - position=(hoffs + scl * (h_extra + position[0] + 12), - v + scl * (v_extra + position[1] + 53)), + position=( + hoffs + scl * (h_extra + position[0]), + v + scl * (v_extra + position[1]), + ), + size=(scl * 60, scl * 80), + color=color, + texture=self._lineup_tex, + model_transparent=self._lineup_1_transparent_model, + ) + ba.imagewidget( + parent=self._root_widget, + draw_controller=btn, + position=( + hoffs + scl * (h_extra + position[0] + 12), + v + scl * (v_extra + position[1] + 53), + ), size=(scl * 36, scl * 18), texture=self._lineup_tex, color=eye_color, - model_transparent=self._eyes_model) + model_transparent=self._eyes_model, + ) elif i == 1: - ba.imagewidget(parent=self._root_widget, - draw_controller=btn, - position=(hoffs + scl * (h_extra + position[0]), - v + scl * (v_extra + position[1])), - size=(scl * 45, scl * 90), - color=color, - texture=self._lineup_tex, - model_transparent=self._lineup_2_transparent_model) - ba.imagewidget(parent=self._root_widget, - draw_controller=btn, - position=(hoffs + scl * (h_extra + position[0] + 5), - v + scl * (v_extra + position[1] + 67)), - size=(scl * 32, scl * 16), - texture=self._lineup_tex, - color=eye_color, - model_transparent=self._eyes_model) + ba.imagewidget( + parent=self._root_widget, + draw_controller=btn, + position=( + hoffs + scl * (h_extra + position[0]), + v + scl * (v_extra + position[1]), + ), + size=(scl * 45, scl * 90), + color=color, + texture=self._lineup_tex, + model_transparent=self._lineup_2_transparent_model, + ) + ba.imagewidget( + parent=self._root_widget, + draw_controller=btn, + position=( + hoffs + scl * (h_extra + position[0] + 5), + v + scl * (v_extra + position[1] + 67), + ), + size=(scl * 32, scl * 16), + texture=self._lineup_tex, + color=eye_color, + model_transparent=self._eyes_model, + ) elif i == 2: - ba.imagewidget(parent=self._root_widget, - draw_controller=btn, - position=(hoffs + scl * (h_extra + position[0]), - v + scl * (v_extra + position[1])), - size=(scl * 45, scl * 90), - color=color, - texture=self._lineup_tex, - model_transparent=self._lineup_3_transparent_model) - ba.imagewidget(parent=self._root_widget, - draw_controller=btn, - position=(hoffs + scl * (h_extra + position[0] + 5), - v + scl * (v_extra + position[1] + 59)), - size=(scl * 34, scl * 17), - texture=self._lineup_tex, - color=eye_color, - model_transparent=self._eyes_model) + ba.imagewidget( + parent=self._root_widget, + draw_controller=btn, + position=( + hoffs + scl * (h_extra + position[0]), + v + scl * (v_extra + position[1]), + ), + size=(scl * 45, scl * 90), + color=color, + texture=self._lineup_tex, + model_transparent=self._lineup_3_transparent_model, + ) + ba.imagewidget( + parent=self._root_widget, + draw_controller=btn, + position=( + hoffs + scl * (h_extra + position[0] + 5), + v + scl * (v_extra + position[1] + 59), + ), + size=(scl * 34, scl * 17), + texture=self._lineup_tex, + color=eye_color, + model_transparent=self._eyes_model, + ) elif i == 3: - ba.imagewidget(parent=self._root_widget, - draw_controller=btn, - position=(hoffs + scl * (h_extra + position[0]), - v + scl * (v_extra + position[1])), - size=(scl * 48, scl * 96), - color=color, - texture=self._lineup_tex, - model_transparent=self._lineup_4_transparent_model) - ba.imagewidget(parent=self._root_widget, - draw_controller=btn, - position=(hoffs + scl * (h_extra + position[0] + 2), - v + scl * (v_extra + position[1] + 62)), - size=(scl * 38, scl * 19), - texture=self._lineup_tex, - color=eye_color, - model_transparent=self._eyes_model) + ba.imagewidget( + parent=self._root_widget, + draw_controller=btn, + position=( + hoffs + scl * (h_extra + position[0]), + v + scl * (v_extra + position[1]), + ), + size=(scl * 48, scl * 96), + color=color, + texture=self._lineup_tex, + model_transparent=self._lineup_4_transparent_model, + ) + ba.imagewidget( + parent=self._root_widget, + draw_controller=btn, + position=( + hoffs + scl * (h_extra + position[0] + 2), + v + scl * (v_extra + position[1] + 62), + ), + size=(scl * 38, scl * 19), + texture=self._lineup_tex, + color=eye_color, + model_transparent=self._eyes_model, + ) def _save_state(self) -> None: try: @@ -586,8 +728,11 @@ class PlayWindow(ba.Window): elif sel_name == 'Back': sel = self._back_button else: - sel = (self._coop_button if self._coop_button is not None else - self._teams_button) + sel = ( + self._coop_button + if self._coop_button is not None + else self._teams_button + ) ba.containerwidget(edit=self._root_widget, selected_child=sel) except Exception: ba.print_exception(f'Error restoring state for {self}.') diff --git a/dist/ba_data/python/bastd/ui/playlist/__init__.py b/dist/ba_data/python/bastd/ui/playlist/__init__.py index 5aeadf6..163487a 100644 --- a/dist/ba_data/python/bastd/ui/playlist/__init__.py +++ b/dist/ba_data/python/bastd/ui/playlist/__init__.py @@ -17,37 +17,48 @@ class PlaylistTypeVars: """Defines values for a playlist type (config names to use, etc).""" def __init__(self, sessiontype: type[ba.Session]): - from ba.internal import (get_default_teams_playlist, - get_default_free_for_all_playlist) + from ba.internal import ( + get_default_teams_playlist, + get_default_free_for_all_playlist, + ) + self.sessiontype: type[ba.Session] if issubclass(sessiontype, ba.DualTeamSession): - play_mode_name = ba.Lstr(resource='playModes.teamsText', - fallback_resource='teamsText') + play_mode_name = ba.Lstr( + resource='playModes.teamsText', fallback_resource='teamsText' + ) self.get_default_list_call = get_default_teams_playlist self.session_type_name = 'ba.DualTeamSession' self.config_name = 'Team Tournament' - self.window_title_name = ba.Lstr(resource='playModes.teamsText', - fallback_resource='teamsText') + self.window_title_name = ba.Lstr( + resource='playModes.teamsText', fallback_resource='teamsText' + ) self.sessiontype = ba.DualTeamSession elif issubclass(sessiontype, ba.FreeForAllSession): - play_mode_name = ba.Lstr(resource='playModes.freeForAllText', - fallback_resource='freeForAllText') + play_mode_name = ba.Lstr( + resource='playModes.freeForAllText', + fallback_resource='freeForAllText', + ) self.get_default_list_call = get_default_free_for_all_playlist self.session_type_name = 'ba.FreeForAllSession' self.config_name = 'Free-for-All' self.window_title_name = ba.Lstr( resource='playModes.freeForAllText', - fallback_resource='freeForAllText') + fallback_resource='freeForAllText', + ) self.sessiontype = ba.FreeForAllSession else: raise RuntimeError( - f'Playlist type vars undefined for sessiontype: {sessiontype}') - self.default_list_name = ba.Lstr(resource='defaultGameListNameText', - subs=[('${PLAYMODE}', play_mode_name) - ]) + f'Playlist type vars undefined for sessiontype: {sessiontype}' + ) + self.default_list_name = ba.Lstr( + resource='defaultGameListNameText', + subs=[('${PLAYMODE}', play_mode_name)], + ) self.default_new_list_name = ba.Lstr( resource='defaultNewGameListNameText', - subs=[('${PLAYMODE}', play_mode_name)]) + subs=[('${PLAYMODE}', play_mode_name)], + ) diff --git a/dist/ba_data/python/bastd/ui/playlist/addgame.py b/dist/ba_data/python/bastd/ui/playlist/addgame.py index 17af366..5d1aad2 100644 --- a/dist/ba_data/python/bastd/ui/playlist/addgame.py +++ b/dist/ba_data/python/bastd/ui/playlist/addgame.py @@ -16,36 +16,52 @@ if TYPE_CHECKING: class PlaylistAddGameWindow(ba.Window): """Window for selecting a game type to add to a playlist.""" - def __init__(self, - editcontroller: PlaylistEditController, - transition: str = 'in_right'): + def __init__( + self, + editcontroller: PlaylistEditController, + transition: str = 'in_right', + ): self._editcontroller = editcontroller self._r = 'addGameWindow' uiscale = ba.app.ui.uiscale self._width = 750 if uiscale is ba.UIScale.SMALL else 650 x_inset = 50 if uiscale is ba.UIScale.SMALL else 0 - self._height = (346 if uiscale is ba.UIScale.SMALL else - 380 if uiscale is ba.UIScale.MEDIUM else 440) + self._height = ( + 346 + if uiscale is ba.UIScale.SMALL + else 380 + if uiscale is ba.UIScale.MEDIUM + else 440 + ) top_extra = 30 if uiscale is ba.UIScale.SMALL else 20 self._scroll_width = 210 - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height + top_extra), - transition=transition, - scale=(2.17 if uiscale is ba.UIScale.SMALL else - 1.5 if uiscale is ba.UIScale.MEDIUM else 1.0), - stack_offset=(0, 1) if uiscale is ba.UIScale.SMALL else (0, 0))) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height + top_extra), + transition=transition, + scale=( + 2.17 + if uiscale is ba.UIScale.SMALL + else 1.5 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + stack_offset=(0, 1) if uiscale is ba.UIScale.SMALL else (0, 0), + ) + ) - self._back_button = ba.buttonwidget(parent=self._root_widget, - position=(58 + x_inset, - self._height - 53), - size=(165, 70), - scale=0.75, - text_scale=1.2, - label=ba.Lstr(resource='backText'), - autoselect=True, - button_type='back', - on_activate_call=self._back) + self._back_button = ba.buttonwidget( + parent=self._root_widget, + position=(58 + x_inset, self._height - 53), + size=(165, 70), + scale=0.75, + text_scale=1.2, + label=ba.Lstr(resource='backText'), + autoselect=True, + button_type='back', + on_activate_call=self._back, + ) self._select_button = select_button = ba.buttonwidget( parent=self._root_widget, position=(self._width - (172 + x_inset), self._height - 50), @@ -54,22 +70,26 @@ class PlaylistAddGameWindow(ba.Window): scale=0.75, text_scale=1.2, label=ba.Lstr(resource='selectText'), - on_activate_call=self._add) + on_activate_call=self._add, + ) if ba.app.ui.use_toolbars: ba.widget( edit=select_button, - right_widget=ba.internal.get_special_widget('party_button')) + right_widget=ba.internal.get_special_widget('party_button'), + ) - ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, self._height - 28), - size=(0, 0), - scale=1.0, - text=ba.Lstr(resource=self._r + '.titleText'), - h_align='center', - color=ba.app.ui.title_color, - maxwidth=250, - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, self._height - 28), + size=(0, 0), + scale=1.0, + text=ba.Lstr(resource=self._r + '.titleText'), + h_align='center', + color=ba.app.ui.title_color, + maxwidth=250, + v_align='center', + ) v = self._height - 64 self._selected_title_text = ba.textwidget( @@ -80,7 +100,8 @@ class PlaylistAddGameWindow(ba.Window): color=(0.7, 1.0, 0.7, 1.0), maxwidth=self._width - self._scroll_width - 150 - x_inset * 2, h_align='left', - v_align='center') + v_align='center', + ) v -= 30 self._selected_description_text = ba.textwidget( @@ -90,46 +111,55 @@ class PlaylistAddGameWindow(ba.Window): scale=0.7, color=(0.5, 0.8, 0.5, 1.0), maxwidth=self._width - self._scroll_width - 150 - x_inset * 2, - h_align='left') + h_align='left', + ) scroll_height = self._height - 100 v = self._height - 60 - self._scrollwidget = ba.scrollwidget(parent=self._root_widget, - position=(x_inset + 61, - v - scroll_height), - size=(self._scroll_width, - scroll_height), - highlight=False) - ba.widget(edit=self._scrollwidget, - up_widget=self._back_button, - left_widget=self._back_button, - right_widget=select_button) + self._scrollwidget = ba.scrollwidget( + parent=self._root_widget, + position=(x_inset + 61, v - scroll_height), + size=(self._scroll_width, scroll_height), + highlight=False, + ) + ba.widget( + edit=self._scrollwidget, + up_widget=self._back_button, + left_widget=self._back_button, + right_widget=select_button, + ) self._column: ba.Widget | None = None v -= 35 - ba.containerwidget(edit=self._root_widget, - cancel_button=self._back_button, - start_button=select_button) + ba.containerwidget( + edit=self._root_widget, + cancel_button=self._back_button, + start_button=select_button, + ) self._selected_game_type: type[ba.GameActivity] | None = None - ba.containerwidget(edit=self._root_widget, - selected_child=self._scrollwidget) + ba.containerwidget( + edit=self._root_widget, selected_child=self._scrollwidget + ) self._game_types: list[type[ba.GameActivity]] = [] # Get actual games loading in the bg. - ba.app.meta.load_exported_classes(ba.GameActivity, - self._on_game_types_loaded, - completion_cb_in_bg_thread=True) + ba.app.meta.load_exported_classes( + ba.GameActivity, + self._on_game_types_loaded, + completion_cb_in_bg_thread=True, + ) # Refresh with our initial empty list. We'll refresh again once # game loading is complete. self._refresh() - def _on_game_types_loaded(self, - gametypes: list[type[ba.GameActivity]]) -> None: + def _on_game_types_loaded( + self, gametypes: list[type[ba.GameActivity]] + ) -> None: from ba.internal import get_unowned_game_types # We asked for a bg thread completion cb so we can do some @@ -138,7 +168,8 @@ class PlaylistAddGameWindow(ba.Window): sessiontype = self._editcontroller.get_session_type() unowned = get_unowned_game_types() self._game_types = [ - gt for gt in gametypes + gt + for gt in gametypes if gt not in unowned and gt.supports_session_type(sessiontype) ] @@ -154,31 +185,34 @@ class PlaylistAddGameWindow(ba.Window): if self._column is not None: self._column.delete() - self._column = ba.columnwidget(parent=self._scrollwidget, - border=2, - margin=0) + self._column = ba.columnwidget( + parent=self._scrollwidget, border=2, margin=0 + ) for i, gametype in enumerate(self._game_types): def _doit() -> None: if self._select_button: - ba.timer(0.1, - self._select_button.activate, - timetype=ba.TimeType.REAL) + ba.timer( + 0.1, + self._select_button.activate, + timetype=ba.TimeType.REAL, + ) - txt = ba.textwidget(parent=self._column, - position=(0, 0), - size=(self._width - 88, 24), - text=gametype.get_display_string(), - h_align='left', - v_align='center', - color=(0.8, 0.8, 0.8, 1.0), - maxwidth=self._scroll_width * 0.8, - on_select_call=ba.Call( - self._set_selected_game_type, gametype), - always_highlight=True, - selectable=True, - on_activate_call=_doit) + txt = ba.textwidget( + parent=self._column, + position=(0, 0), + size=(self._width - 88, 24), + text=gametype.get_display_string(), + h_align='left', + v_align='center', + color=(0.8, 0.8, 0.8, 1.0), + maxwidth=self._scroll_width * 0.8, + on_select_call=ba.Call(self._set_selected_game_type, gametype), + always_highlight=True, + selectable=True, + on_activate_call=_doit, + ) if i == 0: ba.widget(edit=txt, up_widget=self._back_button) @@ -189,22 +223,28 @@ class PlaylistAddGameWindow(ba.Window): color=(0.54, 0.52, 0.67), textcolor=(0.7, 0.65, 0.7), on_activate_call=self._on_get_more_games_press, - size=(178, 50)) + size=(178, 50), + ) if select_get_more_games_button: - ba.containerwidget(edit=self._column, - selected_child=self._get_more_games_button, - visible_child=self._get_more_games_button) + ba.containerwidget( + edit=self._column, + selected_child=self._get_more_games_button, + visible_child=self._get_more_games_button, + ) def _on_get_more_games_press(self) -> None: from bastd.ui.account import show_sign_in_prompt from bastd.ui.store.browser import StoreBrowserWindow + if ba.internal.get_v1_account_state() != 'signed_in': show_sign_in_prompt() return - StoreBrowserWindow(modal=True, - show_tab=StoreBrowserWindow.TabID.MINIGAMES, - on_close_call=self._on_store_close, - origin_widget=self._get_more_games_button) + StoreBrowserWindow( + modal=True, + show_tab=StoreBrowserWindow.TabID.MINIGAMES, + on_close_call=self._on_store_close, + origin_widget=self._get_more_games_button, + ) def _on_store_close(self) -> None: self._refresh(select_get_more_games_button=True) @@ -217,11 +257,15 @@ class PlaylistAddGameWindow(ba.Window): def _set_selected_game_type(self, gametype: type[ba.GameActivity]) -> None: self._selected_game_type = gametype - ba.textwidget(edit=self._selected_title_text, - text=gametype.get_display_string()) - ba.textwidget(edit=self._selected_description_text, - text=gametype.get_description_display_string( - self._editcontroller.get_session_type())) + ba.textwidget( + edit=self._selected_title_text, text=gametype.get_display_string() + ) + ba.textwidget( + edit=self._selected_description_text, + text=gametype.get_description_display_string( + self._editcontroller.get_session_type() + ), + ) def _back(self) -> None: self._editcontroller.add_game_cancelled() diff --git a/dist/ba_data/python/bastd/ui/playlist/browser.py b/dist/ba_data/python/bastd/ui/playlist/browser.py index 8a62126..1ee3f11 100644 --- a/dist/ba_data/python/bastd/ui/playlist/browser.py +++ b/dist/ba_data/python/bastd/ui/playlist/browser.py @@ -18,10 +18,12 @@ if TYPE_CHECKING: class PlaylistBrowserWindow(ba.Window): """Window for starting teams games.""" - def __init__(self, - sessiontype: type[ba.Session], - transition: str | None = 'in_right', - origin_widget: ba.Widget | None = None): + def __init__( + self, + sessiontype: type[ba.Session], + transition: str | None = 'in_right', + origin_widget: ba.Widget | None = None, + ): # pylint: disable=too-many-statements # pylint: disable=cyclic-import from bastd.ui.playlist import PlaylistTypeVars @@ -56,25 +58,41 @@ class PlaylistBrowserWindow(ba.Window): self._ensure_standard_playlists_exist() # Get the current selection (if any). - self._selected_playlist = ba.app.config.get(self._pvars.config_name + - ' Playlist Selection') + self._selected_playlist = ba.app.config.get( + self._pvars.config_name + ' Playlist Selection' + ) uiscale = ba.app.ui.uiscale self._width = 900 if uiscale is ba.UIScale.SMALL else 800 x_inset = 50 if uiscale is ba.UIScale.SMALL else 0 - self._height = (480 if uiscale is ba.UIScale.SMALL else - 510 if uiscale is ba.UIScale.MEDIUM else 580) + self._height = ( + 480 + if uiscale is ba.UIScale.SMALL + else 510 + if uiscale is ba.UIScale.MEDIUM + else 580 + ) top_extra = 20 if uiscale is ba.UIScale.SMALL else 0 - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height + top_extra), - transition=transition, - toolbar_visibility='menu_full', - scale_origin_stack_offset=scale_origin, - scale=(1.69 if uiscale is ba.UIScale.SMALL else - 1.05 if uiscale is ba.UIScale.MEDIUM else 0.9), - stack_offset=(0, -26) if uiscale is ba.UIScale.SMALL else (0, 0))) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height + top_extra), + transition=transition, + toolbar_visibility='menu_full', + scale_origin_stack_offset=scale_origin, + scale=( + 1.69 + if uiscale is ba.UIScale.SMALL + else 1.05 + if uiscale is ba.UIScale.MEDIUM + else 0.9 + ), + stack_offset=(0, -26) + if uiscale is ba.UIScale.SMALL + else (0, 0), + ) + ) self._back_button: ba.Widget | None = ba.buttonwidget( parent=self._root_widget, @@ -84,9 +102,11 @@ class PlaylistBrowserWindow(ba.Window): on_activate_call=self._on_back_press, autoselect=True, label=ba.Lstr(resource='backText'), - button_type='back') - ba.containerwidget(edit=self._root_widget, - cancel_button=self._back_button) + button_type='back', + ) + ba.containerwidget( + edit=self._root_widget, cancel_button=self._back_button + ) txt = self._title_text = ba.textwidget( parent=self._root_widget, position=(self._width * 0.5, self._height - 41), @@ -96,34 +116,43 @@ class PlaylistBrowserWindow(ba.Window): res_scale=1.5, color=ba.app.ui.heading_color, h_align='center', - v_align='center') + v_align='center', + ) if uiscale is ba.UIScale.SMALL and ba.app.ui.use_toolbars: ba.textwidget(edit=txt, text='') - ba.buttonwidget(edit=self._back_button, - button_type='backSmall', - size=(60, 54), - position=(59 + x_inset, self._height - 67), - label=ba.charstr(ba.SpecialChar.BACK)) + ba.buttonwidget( + edit=self._back_button, + button_type='backSmall', + size=(60, 54), + position=(59 + x_inset, self._height - 67), + label=ba.charstr(ba.SpecialChar.BACK), + ) if uiscale is ba.UIScale.SMALL and ba.app.ui.use_toolbars: self._back_button.delete() self._back_button = None - ba.containerwidget(edit=self._root_widget, - on_cancel_call=self._on_back_press) + ba.containerwidget( + edit=self._root_widget, on_cancel_call=self._on_back_press + ) scroll_offs = 33 else: scroll_offs = 0 self._scroll_width = self._width - (100 + 2 * x_inset) - self._scroll_height = (self._height - - (146 if uiscale is ba.UIScale.SMALL - and ba.app.ui.use_toolbars else 136)) + self._scroll_height = self._height - ( + 146 + if uiscale is ba.UIScale.SMALL and ba.app.ui.use_toolbars + else 136 + ) self._scrollwidget = ba.scrollwidget( parent=self._root_widget, highlight=False, size=(self._scroll_width, self._scroll_height), - position=((self._width - self._scroll_width) * 0.5, - 65 + scroll_offs)) + position=( + (self._width - self._scroll_width) * 0.5, + 65 + scroll_offs, + ), + ) ba.containerwidget(edit=self._scrollwidget, claims_left_right=True) self._subcontainer: ba.Widget | None = None self._config_name_full = self._pvars.config_name + ' Playlists' @@ -132,154 +161,168 @@ class PlaylistBrowserWindow(ba.Window): # Update now and once per second. # (this should do our initial refresh) self._update() - self._update_timer = ba.Timer(1.0, - ba.WeakCall(self._update), - timetype=ba.TimeType.REAL, - repeat=True) + self._update_timer = ba.Timer( + 1.0, + ba.WeakCall(self._update), + timetype=ba.TimeType.REAL, + repeat=True, + ) def _ensure_standard_playlists_exist(self) -> None: # On new installations, go ahead and create a few playlists # besides the hard-coded default one: - if not ba.internal.get_v1_account_misc_val('madeStandardPlaylists', - False): - ba.internal.add_transaction({ - 'type': - 'ADD_PLAYLIST', - 'playlistType': - 'Free-for-All', - 'playlistName': - ba.Lstr(resource='singleGamePlaylistNameText' - ).evaluate().replace( - '${GAME}', - ba.Lstr(translate=('gameNames', - 'Death Match')).evaluate()), - 'playlist': [ - { - 'type': 'bs_death_match.DeathMatchGame', - 'settings': { - 'Epic Mode': False, - 'Kills to Win Per Player': 10, - 'Respawn Times': 1.0, - 'Time Limit': 300, - 'map': 'Doom Shroom' - } - }, - { - 'type': 'bs_death_match.DeathMatchGame', - 'settings': { - 'Epic Mode': False, - 'Kills to Win Per Player': 10, - 'Respawn Times': 1.0, - 'Time Limit': 300, - 'map': 'Crag Castle' - } - }, - ] - }) - ba.internal.add_transaction({ - 'type': - 'ADD_PLAYLIST', - 'playlistType': - 'Team Tournament', - 'playlistName': - ba.Lstr( + if not ba.internal.get_v1_account_misc_val( + 'madeStandardPlaylists', False + ): + ba.internal.add_transaction( + { + 'type': 'ADD_PLAYLIST', + 'playlistType': 'Free-for-All', + 'playlistName': ba.Lstr( resource='singleGamePlaylistNameText' - ).evaluate().replace( + ) + .evaluate() + .replace( '${GAME}', - ba.Lstr(translate=('gameNames', - 'Capture the Flag')).evaluate()), - 'playlist': [ - { - 'type': 'bs_capture_the_flag.CTFGame', - 'settings': { - 'map': 'Bridgit', - 'Score to Win': 3, - 'Flag Idle Return Time': 30, - 'Flag Touch Return Time': 0, - 'Respawn Times': 1.0, - 'Time Limit': 600, - 'Epic Mode': False + ba.Lstr( + translate=('gameNames', 'Death Match') + ).evaluate(), + ), + 'playlist': [ + { + 'type': 'bs_death_match.DeathMatchGame', + 'settings': { + 'Epic Mode': False, + 'Kills to Win Per Player': 10, + 'Respawn Times': 1.0, + 'Time Limit': 300, + 'map': 'Doom Shroom', + }, + }, + { + 'type': 'bs_death_match.DeathMatchGame', + 'settings': { + 'Epic Mode': False, + 'Kills to Win Per Player': 10, + 'Respawn Times': 1.0, + 'Time Limit': 300, + 'map': 'Crag Castle', + }, + }, + ], + } + ) + ba.internal.add_transaction( + { + 'type': 'ADD_PLAYLIST', + 'playlistType': 'Team Tournament', + 'playlistName': ba.Lstr( + resource='singleGamePlaylistNameText' + ) + .evaluate() + .replace( + '${GAME}', + ba.Lstr( + translate=('gameNames', 'Capture the Flag') + ).evaluate(), + ), + 'playlist': [ + { + 'type': 'bs_capture_the_flag.CTFGame', + 'settings': { + 'map': 'Bridgit', + 'Score to Win': 3, + 'Flag Idle Return Time': 30, + 'Flag Touch Return Time': 0, + 'Respawn Times': 1.0, + 'Time Limit': 600, + 'Epic Mode': False, + }, + }, + { + 'type': 'bs_capture_the_flag.CTFGame', + 'settings': { + 'map': 'Roundabout', + 'Score to Win': 2, + 'Flag Idle Return Time': 30, + 'Flag Touch Return Time': 0, + 'Respawn Times': 1.0, + 'Time Limit': 600, + 'Epic Mode': False, + }, + }, + { + 'type': 'bs_capture_the_flag.CTFGame', + 'settings': { + 'map': 'Tip Top', + 'Score to Win': 2, + 'Flag Idle Return Time': 30, + 'Flag Touch Return Time': 3, + 'Respawn Times': 1.0, + 'Time Limit': 300, + 'Epic Mode': False, + }, + }, + ], + } + ) + ba.internal.add_transaction( + { + 'type': 'ADD_PLAYLIST', + 'playlistType': 'Team Tournament', + 'playlistName': ba.Lstr( + translate=('playlistNames', 'Just Sports') + ).evaluate(), + 'playlist': [ + { + 'type': 'bs_hockey.HockeyGame', + 'settings': { + 'Time Limit': 0, + 'map': 'Hockey Stadium', + 'Score to Win': 1, + 'Respawn Times': 1.0, + }, + }, + { + 'type': 'bs_football.FootballTeamGame', + 'settings': { + 'Time Limit': 0, + 'map': 'Football Stadium', + 'Score to Win': 21, + 'Respawn Times': 1.0, + }, + }, + ], + } + ) + ba.internal.add_transaction( + { + 'type': 'ADD_PLAYLIST', + 'playlistType': 'Free-for-All', + 'playlistName': ba.Lstr( + translate=('playlistNames', 'Just Epic') + ).evaluate(), + 'playlist': [ + { + 'type': 'bs_elimination.EliminationGame', + 'settings': { + 'Time Limit': 120, + 'map': 'Tip Top', + 'Respawn Times': 1.0, + 'Lives Per Player': 1, + 'Epic Mode': 1, + }, } - }, - { - 'type': 'bs_capture_the_flag.CTFGame', - 'settings': { - 'map': 'Roundabout', - 'Score to Win': 2, - 'Flag Idle Return Time': 30, - 'Flag Touch Return Time': 0, - 'Respawn Times': 1.0, - 'Time Limit': 600, - 'Epic Mode': False - } - }, - { - 'type': 'bs_capture_the_flag.CTFGame', - 'settings': { - 'map': 'Tip Top', - 'Score to Win': 2, - 'Flag Idle Return Time': 30, - 'Flag Touch Return Time': 3, - 'Respawn Times': 1.0, - 'Time Limit': 300, - 'Epic Mode': False - } - }, - ] - }) - ba.internal.add_transaction({ - 'type': - 'ADD_PLAYLIST', - 'playlistType': - 'Team Tournament', - 'playlistName': - ba.Lstr(translate=('playlistNames', 'Just Sports') - ).evaluate(), - 'playlist': [ - { - 'type': 'bs_hockey.HockeyGame', - 'settings': { - 'Time Limit': 0, - 'map': 'Hockey Stadium', - 'Score to Win': 1, - 'Respawn Times': 1.0 - } - }, - { - 'type': 'bs_football.FootballTeamGame', - 'settings': { - 'Time Limit': 0, - 'map': 'Football Stadium', - 'Score to Win': 21, - 'Respawn Times': 1.0 - } - }, - ] - }) - ba.internal.add_transaction({ - 'type': - 'ADD_PLAYLIST', - 'playlistType': - 'Free-for-All', - 'playlistName': - ba.Lstr(translate=('playlistNames', 'Just Epic') - ).evaluate(), - 'playlist': [{ - 'type': 'bs_elimination.EliminationGame', - 'settings': { - 'Time Limit': 120, - 'map': 'Tip Top', - 'Respawn Times': 1.0, - 'Lives Per Player': 1, - 'Epic Mode': 1 - } - }] - }) - ba.internal.add_transaction({ - 'type': 'SET_MISC_VAL', - 'name': 'madeStandardPlaylists', - 'value': True - }) + ], + } + ) + ba.internal.add_transaction( + { + 'type': 'SET_MISC_VAL', + 'name': 'madeStandardPlaylists', + 'value': True, + } + ) ba.internal.run_transactions() def _refresh(self) -> None: @@ -290,6 +333,7 @@ class PlaylistBrowserWindow(ba.Window): # pylint: disable=too-many-nested-blocks from efro.util import asserttype from ba.internal import get_map_class, filter_playlist + if not self._root_widget: return if self._subcontainer is not None: @@ -303,8 +347,10 @@ class PlaylistBrowserWindow(ba.Window): items = list(ba.app.config[self._config_name_full].items()) # Make sure everything is unicode. - items = [(i[0].decode(), i[1]) if not isinstance(i[0], str) else i - for i in items] + items = [ + (i[0].decode(), i[1]) if not isinstance(i[0], str) else i + for i in items + ] items.sort(key=lambda x2: asserttype(x2[0], str).lower()) items = [['__default__', None]] + items # default is always first @@ -318,28 +364,32 @@ class PlaylistBrowserWindow(ba.Window): button_buffer_v = 0 self._sub_width = self._scroll_width - self._sub_height = 40 + rows * (button_height + - 2 * button_buffer_v) + 90 + self._sub_height = ( + 40 + rows * (button_height + 2 * button_buffer_v) + 90 + ) assert self._sub_width is not None assert self._sub_height is not None - self._subcontainer = ba.containerwidget(parent=self._scrollwidget, - size=(self._sub_width, - self._sub_height), - background=False) + self._subcontainer = ba.containerwidget( + parent=self._scrollwidget, + size=(self._sub_width, self._sub_height), + background=False, + ) children = self._subcontainer.get_children() for child in children: child.delete() - ba.textwidget(parent=self._subcontainer, - text=ba.Lstr(resource='playlistsText'), - position=(40, self._sub_height - 26), - size=(0, 0), - scale=1.0, - maxwidth=400, - color=ba.app.ui.title_color, - h_align='left', - v_align='center') + ba.textwidget( + parent=self._subcontainer, + text=ba.Lstr(resource='playlistsText'), + position=(40, self._sub_height - 26), + size=(0, 0), + scale=1.0, + maxwidth=400, + color=ba.app.ui.title_color, + h_align='left', + v_align='center', + ) index = 0 appconfig = ba.app.config @@ -356,37 +406,61 @@ class PlaylistBrowserWindow(ba.Window): for x in range(columns): name = items[index][0] assert name is not None - pos = (x * (button_width + 2 * button_buffer_h) + - button_buffer_h + 8 + h_offs, self._sub_height - 47 - - (y + 1) * (button_height + 2 * button_buffer_v)) - btn = ba.buttonwidget(parent=self._subcontainer, - button_type='square', - size=(button_width, button_height), - autoselect=True, - label='', - position=pos) + pos = ( + x * (button_width + 2 * button_buffer_h) + + button_buffer_h + + 8 + + h_offs, + self._sub_height + - 47 + - (y + 1) * (button_height + 2 * button_buffer_v), + ) + btn = ba.buttonwidget( + parent=self._subcontainer, + button_type='square', + size=(button_width, button_height), + autoselect=True, + label='', + position=pos, + ) - if (x == 0 and ba.app.ui.use_toolbars - and uiscale is ba.UIScale.SMALL): - ba.widget(edit=btn, - left_widget=ba.internal.get_special_widget( - 'back_button')) - if (x == columns - 1 and ba.app.ui.use_toolbars - and uiscale is ba.UIScale.SMALL): - ba.widget(edit=btn, - right_widget=ba.internal.get_special_widget( - 'party_button')) + if ( + x == 0 + and ba.app.ui.use_toolbars + and uiscale is ba.UIScale.SMALL + ): + ba.widget( + edit=btn, + left_widget=ba.internal.get_special_widget( + 'back_button' + ), + ) + if ( + x == columns - 1 + and ba.app.ui.use_toolbars + and uiscale is ba.UIScale.SMALL + ): + ba.widget( + edit=btn, + right_widget=ba.internal.get_special_widget( + 'party_button' + ), + ) ba.buttonwidget( edit=btn, - on_activate_call=ba.Call(self._on_playlist_press, btn, - name), - on_select_call=ba.Call(self._on_playlist_select, name)) + on_activate_call=ba.Call( + self._on_playlist_press, btn, name + ), + on_select_call=ba.Call(self._on_playlist_select, name), + ) ba.widget(edit=btn, show_buffer_top=50, show_buffer_bottom=50) if self._selected_playlist == name: - ba.containerwidget(edit=self._subcontainer, - selected_child=btn, - visible_child=btn) + ba.containerwidget( + edit=self._subcontainer, + selected_child=btn, + visible_child=btn, + ) if self._back_button is not None: if y == 0: @@ -399,16 +473,20 @@ class PlaylistBrowserWindow(ba.Window): print_name = self._pvars.default_list_name else: print_name = name - ba.textwidget(parent=self._subcontainer, - text=print_name, - position=(pos[0] + button_width * 0.5, - pos[1] + button_height * 0.79), - size=(0, 0), - scale=button_width * 0.003, - maxwidth=button_width * 0.7, - draw_controller=btn, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._subcontainer, + text=print_name, + position=( + pos[0] + button_width * 0.5, + pos[1] + button_height * 0.79, + ), + size=(0, 0), + scale=button_width * 0.003, + maxwidth=button_width * 0.7, + draw_controller=btn, + h_align='center', + v_align='center', + ) # Poke into this playlist and see if we can display some of # its maps. @@ -419,19 +497,28 @@ class PlaylistBrowserWindow(ba.Window): if name == '__default__': playlist = self._pvars.get_default_list_call() else: - if name not in appconfig[self._pvars.config_name + - ' Playlists']: + if ( + name + not in appconfig[ + self._pvars.config_name + ' Playlists' + ] + ): print( 'NOT FOUND ERR', - appconfig[self._pvars.config_name + - ' Playlists']) - playlist = appconfig[self._pvars.config_name + - ' Playlists'][name] - playlist = filter_playlist(playlist, - self._sessiontype, - remove_unowned=False, - mark_unowned=True, - name=name) + appconfig[ + self._pvars.config_name + ' Playlists' + ], + ) + playlist = appconfig[ + self._pvars.config_name + ' Playlists' + ][name] + playlist = filter_playlist( + playlist, + self._sessiontype, + remove_unowned=False, + mark_unowned=True, + name=name, + ) for entry in playlist: mapname = entry['settings']['map'] maptype: type[ba.Map] | None @@ -479,10 +566,16 @@ class PlaylistBrowserWindow(ba.Window): if tex_index < len(map_textures): entry = map_texture_entries[tex_index] - owned = not (('is_unowned_map' in entry - and entry['is_unowned_map']) or - ('is_unowned_game' in entry - and entry['is_unowned_game'])) + owned = not ( + ( + 'is_unowned_map' in entry + and entry['is_unowned_map'] + ) + or ( + 'is_unowned_game' in entry + and entry['is_unowned_game'] + ) + ) tex_name = map_textures[tex_index] h = pos[0] + h_offs_img + scl * 250 * col @@ -497,14 +590,17 @@ class PlaylistBrowserWindow(ba.Window): draw_controller=btn, model_opaque=model_opaque, model_transparent=model_transparent, - mask_texture=mask_tex)) + mask_texture=mask_tex, + ) + ) if not owned: ba.imagewidget( parent=self._subcontainer, size=(scl * 100.0, scl * 100.0), position=(h + scl * 75, v + scl * 10), texture=ba.gettexture('lock'), - draw_controller=btn) + draw_controller=btn, + ) if v is not None: v -= scl * 130.0 @@ -512,16 +608,20 @@ class PlaylistBrowserWindow(ba.Window): ba.print_exception('Error listing playlist maps.') if not map_images: - ba.textwidget(parent=self._subcontainer, - text='???', - scale=1.5, - size=(0, 0), - color=(1, 1, 1, 0.5), - h_align='center', - v_align='center', - draw_controller=btn, - position=(pos[0] + button_width * 0.5, - pos[1] + button_height * 0.5)) + ba.textwidget( + parent=self._subcontainer, + text='???', + scale=1.5, + size=(0, 0), + color=(1, 1, 1, 0.5), + h_align='center', + v_align='center', + draw_controller=btn, + position=( + pos[0] + button_width * 0.5, + pos[1] + button_height * 0.5, + ), + ) index += 1 @@ -538,7 +638,8 @@ class PlaylistBrowserWindow(ba.Window): on_activate_call=self._on_customize_press, color=(0.54, 0.52, 0.67), textcolor=(0.7, 0.65, 0.7), - autoselect=True) + autoselect=True, + ) ba.widget(edit=btn, show_buffer_top=22, show_buffer_bottom=28) self._restore_state() @@ -562,34 +663,40 @@ class PlaylistBrowserWindow(ba.Window): self._last_config = copy.deepcopy(cfg) self._refresh() - def _on_playlist_press(self, button: ba.Widget, - playlist_name: str) -> None: + def _on_playlist_press(self, button: ba.Widget, playlist_name: str) -> None: # pylint: disable=cyclic-import from bastd.ui.playoptions import PlayOptionsWindow # Make sure the target playlist still exists. - exists = (playlist_name == '__default__' - or playlist_name in ba.app.config.get( - self._config_name_full, {})) + exists = ( + playlist_name == '__default__' + or playlist_name in ba.app.config.get(self._config_name_full, {}) + ) if not exists: return self._save_state() - PlayOptionsWindow(sessiontype=self._sessiontype, - scale_origin=button.get_screen_space_center(), - playlist=playlist_name, - delegate=self) + PlayOptionsWindow( + sessiontype=self._sessiontype, + scale_origin=button.get_screen_space_center(), + playlist=playlist_name, + delegate=self, + ) def _on_customize_press(self) -> None: # pylint: disable=cyclic-import from bastd.ui.playlist.customizebrowser import ( - PlaylistCustomizeBrowserWindow) + PlaylistCustomizeBrowserWindow, + ) + self._save_state() ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.ui.set_main_menu_window( PlaylistCustomizeBrowserWindow( origin_widget=self._customize_button, - sessiontype=self._sessiontype).get_root_widget()) + sessiontype=self._sessiontype, + ).get_root_widget() + ) def _on_back_press(self) -> None: # pylint: disable=cyclic-import @@ -597,19 +704,23 @@ class PlaylistBrowserWindow(ba.Window): # Store our selected playlist if that's changed. if self._selected_playlist is not None: - prev_sel = ba.app.config.get(self._pvars.config_name + - ' Playlist Selection') + prev_sel = ba.app.config.get( + self._pvars.config_name + ' Playlist Selection' + ) if self._selected_playlist != prev_sel: cfg = ba.app.config - cfg[self._pvars.config_name + - ' Playlist Selection'] = self._selected_playlist + cfg[ + self._pvars.config_name + ' Playlist Selection' + ] = self._selected_playlist cfg.commit() self._save_state() - ba.containerwidget(edit=self._root_widget, - transition=self._transition_out) + ba.containerwidget( + edit=self._root_widget, transition=self._transition_out + ) ba.app.ui.set_main_menu_window( - PlayWindow(transition='in_left').get_root_widget()) + PlayWindow(transition='in_left').get_root_widget() + ) def _save_state(self) -> None: try: @@ -638,9 +749,11 @@ class PlaylistBrowserWindow(ba.Window): sel = self._scrollwidget elif sel_name == 'Customize': sel = self._scrollwidget - ba.containerwidget(edit=self._subcontainer, - selected_child=self._customize_button, - visible_child=self._customize_button) + ba.containerwidget( + edit=self._subcontainer, + selected_child=self._customize_button, + visible_child=self._customize_button, + ) else: sel = self._scrollwidget ba.containerwidget(edit=self._root_widget, selected_child=sel) diff --git a/dist/ba_data/python/bastd/ui/playlist/customizebrowser.py b/dist/ba_data/python/bastd/ui/playlist/customizebrowser.py index 2f99ad0..c8b78a5 100644 --- a/dist/ba_data/python/bastd/ui/playlist/customizebrowser.py +++ b/dist/ba_data/python/bastd/ui/playlist/customizebrowser.py @@ -18,16 +18,19 @@ if TYPE_CHECKING: class PlaylistCustomizeBrowserWindow(ba.Window): """Window for viewing a playlist.""" - def __init__(self, - sessiontype: type[ba.Session], - transition: str = 'in_right', - select_playlist: str | None = None, - origin_widget: ba.Widget | None = None): + def __init__( + self, + sessiontype: type[ba.Session], + transition: str = 'in_right', + select_playlist: str | None = None, + origin_widget: ba.Widget | None = None, + ): # Yes this needs tidying. # pylint: disable=too-many-locals # pylint: disable=too-many-statements # pylint: disable=cyclic-import from bastd.ui import playlist + scale_origin: tuple[float, float] | None if origin_widget is not None: self._transition_out = 'out_scale' @@ -44,17 +47,32 @@ class PlaylistCustomizeBrowserWindow(ba.Window): uiscale = ba.app.ui.uiscale self._width = 750.0 if uiscale is ba.UIScale.SMALL else 650.0 x_inset = 50.0 if uiscale is ba.UIScale.SMALL else 0.0 - self._height = (380.0 if uiscale is ba.UIScale.SMALL else - 420.0 if uiscale is ba.UIScale.MEDIUM else 500.0) + self._height = ( + 380.0 + if uiscale is ba.UIScale.SMALL + else 420.0 + if uiscale is ba.UIScale.MEDIUM + else 500.0 + ) top_extra = 20.0 if uiscale is ba.UIScale.SMALL else 0.0 - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height + top_extra), - transition=transition, - scale_origin_stack_offset=scale_origin, - scale=(2.05 if uiscale is ba.UIScale.SMALL else - 1.5 if uiscale is ba.UIScale.MEDIUM else 1.0), - stack_offset=(0, -10) if uiscale is ba.UIScale.SMALL else (0, 0))) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height + top_extra), + transition=transition, + scale_origin_stack_offset=scale_origin, + scale=( + 2.05 + if uiscale is ba.UIScale.SMALL + else 1.5 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + stack_offset=(0, -10) + if uiscale is ba.UIScale.SMALL + else (0, 0), + ) + ) self._back_button = back_button = btn = ba.buttonwidget( parent=self._root_widget, @@ -64,23 +82,29 @@ class PlaylistCustomizeBrowserWindow(ba.Window): autoselect=True, text_scale=1.3, label=ba.Lstr(resource='backText'), - button_type='back') + button_type='back', + ) - ba.textwidget(parent=self._root_widget, - position=(0, self._height - 47), - size=(self._width, 25), - text=ba.Lstr(resource=self._r + '.titleText', - subs=[('${TYPE}', - self._pvars.window_title_name)]), - color=ba.app.ui.heading_color, - maxwidth=290, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(0, self._height - 47), + size=(self._width, 25), + text=ba.Lstr( + resource=self._r + '.titleText', + subs=[('${TYPE}', self._pvars.window_title_name)], + ), + color=ba.app.ui.heading_color, + maxwidth=290, + h_align='center', + v_align='center', + ) - ba.buttonwidget(edit=btn, - button_type='backSmall', - size=(60, 60), - label=ba.charstr(ba.SpecialChar.BACK)) + ba.buttonwidget( + edit=btn, + button_type='backSmall', + size=(60, 60), + label=ba.charstr(ba.SpecialChar.BACK), + ) v = self._height - 59.0 h = 41 + x_inset @@ -89,8 +113,13 @@ class PlaylistCustomizeBrowserWindow(ba.Window): self._lock_images: list[ba.Widget] = [] lock_tex = ba.gettexture('lock') - scl = (1.1 if uiscale is ba.UIScale.SMALL else - 1.27 if uiscale is ba.UIScale.MEDIUM else 1.57) + scl = ( + 1.1 + if uiscale is ba.UIScale.SMALL + else 1.27 + if uiscale is ba.UIScale.MEDIUM + else 1.57 + ) scl *= 0.63 v -= 65.0 * scl new_button = btn = ba.buttonwidget( @@ -103,14 +132,19 @@ class PlaylistCustomizeBrowserWindow(ba.Window): button_type='square', textcolor=b_textcolor, text_scale=0.7, - label=ba.Lstr(resource='newText', - fallback_resource=self._r + '.newText')) + label=ba.Lstr( + resource='newText', fallback_resource=self._r + '.newText' + ), + ) self._lock_images.append( - ba.imagewidget(parent=self._root_widget, - size=(30, 30), - draw_controller=btn, - position=(h - 10, v + 58.0 * scl - 28), - texture=lock_tex)) + ba.imagewidget( + parent=self._root_widget, + size=(30, 30), + draw_controller=btn, + position=(h - 10, v + 58.0 * scl - 28), + texture=lock_tex, + ) + ) v -= 65.0 * scl self._edit_button = edit_button = btn = ba.buttonwidget( @@ -123,14 +157,19 @@ class PlaylistCustomizeBrowserWindow(ba.Window): textcolor=b_textcolor, button_type='square', text_scale=0.7, - label=ba.Lstr(resource='editText', - fallback_resource=self._r + '.editText')) + label=ba.Lstr( + resource='editText', fallback_resource=self._r + '.editText' + ), + ) self._lock_images.append( - ba.imagewidget(parent=self._root_widget, - size=(30, 30), - draw_controller=btn, - position=(h - 10, v + 58.0 * scl - 28), - texture=lock_tex)) + ba.imagewidget( + parent=self._root_widget, + size=(30, 30), + draw_controller=btn, + position=(h - 10, v + 58.0 * scl - 28), + texture=lock_tex, + ) + ) v -= 65.0 * scl duplicate_button = btn = ba.buttonwidget( @@ -143,14 +182,20 @@ class PlaylistCustomizeBrowserWindow(ba.Window): textcolor=b_textcolor, button_type='square', text_scale=0.7, - label=ba.Lstr(resource='duplicateText', - fallback_resource=self._r + '.duplicateText')) + label=ba.Lstr( + resource='duplicateText', + fallback_resource=self._r + '.duplicateText', + ), + ) self._lock_images.append( - ba.imagewidget(parent=self._root_widget, - size=(30, 30), - draw_controller=btn, - position=(h - 10, v + 58.0 * scl - 28), - texture=lock_tex)) + ba.imagewidget( + parent=self._root_widget, + size=(30, 30), + draw_controller=btn, + position=(h - 10, v + 58.0 * scl - 28), + texture=lock_tex, + ) + ) v -= 65.0 * scl delete_button = btn = ba.buttonwidget( @@ -163,14 +208,19 @@ class PlaylistCustomizeBrowserWindow(ba.Window): textcolor=b_textcolor, button_type='square', text_scale=0.7, - label=ba.Lstr(resource='deleteText', - fallback_resource=self._r + '.deleteText')) + label=ba.Lstr( + resource='deleteText', fallback_resource=self._r + '.deleteText' + ), + ) self._lock_images.append( - ba.imagewidget(parent=self._root_widget, - size=(30, 30), - draw_controller=btn, - position=(h - 10, v + 58.0 * scl - 28), - texture=lock_tex)) + ba.imagewidget( + parent=self._root_widget, + size=(30, 30), + draw_controller=btn, + position=(h - 10, v + 58.0 * scl - 28), + texture=lock_tex, + ) + ) v -= 65.0 * scl self._import_button = ba.buttonwidget( parent=self._root_widget, @@ -182,51 +232,61 @@ class PlaylistCustomizeBrowserWindow(ba.Window): textcolor=b_textcolor, button_type='square', text_scale=0.7, - label=ba.Lstr(resource='importText')) + label=ba.Lstr(resource='importText'), + ) v -= 65.0 * scl - btn = ba.buttonwidget(parent=self._root_widget, - position=(h, v), - size=(90, 58.0 * scl), - on_activate_call=self._share_playlist, - color=b_color, - autoselect=True, - textcolor=b_textcolor, - button_type='square', - text_scale=0.7, - label=ba.Lstr(resource='shareText')) + btn = ba.buttonwidget( + parent=self._root_widget, + position=(h, v), + size=(90, 58.0 * scl), + on_activate_call=self._share_playlist, + color=b_color, + autoselect=True, + textcolor=b_textcolor, + button_type='square', + text_scale=0.7, + label=ba.Lstr(resource='shareText'), + ) self._lock_images.append( - ba.imagewidget(parent=self._root_widget, - size=(30, 30), - draw_controller=btn, - position=(h - 10, v + 58.0 * scl - 28), - texture=lock_tex)) + ba.imagewidget( + parent=self._root_widget, + size=(30, 30), + draw_controller=btn, + position=(h - 10, v + 58.0 * scl - 28), + texture=lock_tex, + ) + ) v = self._height - 75 self._scroll_height = self._height - 119 - scrollwidget = ba.scrollwidget(parent=self._root_widget, - position=(140 + x_inset, - v - self._scroll_height), - size=(self._width - (180 + 2 * x_inset), - self._scroll_height + 10), - highlight=False) + scrollwidget = ba.scrollwidget( + parent=self._root_widget, + position=(140 + x_inset, v - self._scroll_height), + size=(self._width - (180 + 2 * x_inset), self._scroll_height + 10), + highlight=False, + ) ba.widget(edit=back_button, right_widget=scrollwidget) - self._columnwidget = ba.columnwidget(parent=scrollwidget, - border=2, - margin=0) + self._columnwidget = ba.columnwidget( + parent=scrollwidget, border=2, margin=0 + ) h = 145 self._do_randomize_val = ba.app.config.get( - self._pvars.config_name + ' Playlist Randomize', 0) + self._pvars.config_name + ' Playlist Randomize', 0 + ) h += 210 for btn in [new_button, delete_button, edit_button, duplicate_button]: ba.widget(edit=btn, right_widget=scrollwidget) - ba.widget(edit=scrollwidget, - left_widget=new_button, - right_widget=ba.internal.get_special_widget('party_button') - if ba.app.ui.use_toolbars else None) + ba.widget( + edit=scrollwidget, + left_widget=new_button, + right_widget=ba.internal.get_special_widget('party_button') + if ba.app.ui.use_toolbars + else None, + ) # make sure config exists self._config_name_full = self._pvars.config_name + ' Playlists' @@ -246,10 +306,12 @@ class PlaylistCustomizeBrowserWindow(ba.Window): ba.containerwidget(edit=self._root_widget, selected_child=scrollwidget) # Keep our lock images up to date/etc. - self._update_timer = ba.Timer(1.0, - ba.WeakCall(self._update), - timetype=ba.TimeType.REAL, - repeat=True) + self._update_timer = ba.Timer( + 1.0, + ba.WeakCall(self._update), + timetype=ba.TimeType.REAL, + repeat=True, + ) self._update() def _update(self) -> None: @@ -260,18 +322,22 @@ class PlaylistCustomizeBrowserWindow(ba.Window): def _back(self) -> None: # pylint: disable=cyclic-import from bastd.ui.playlist import browser + if self._selected_playlist_name is not None: cfg = ba.app.config - cfg[self._pvars.config_name + - ' Playlist Selection'] = self._selected_playlist_name + cfg[ + self._pvars.config_name + ' Playlist Selection' + ] = self._selected_playlist_name cfg.commit() - ba.containerwidget(edit=self._root_widget, - transition=self._transition_out) + ba.containerwidget( + edit=self._root_widget, transition=self._transition_out + ) ba.app.ui.set_main_menu_window( browser.PlaylistBrowserWindow( - transition='in_left', - sessiontype=self._sessiontype).get_root_widget()) + transition='in_left', sessiontype=self._sessiontype + ).get_root_widget() + ) def _select(self, name: str, index: int) -> None: self._selected_playlist_name = name @@ -284,6 +350,7 @@ class PlaylistCustomizeBrowserWindow(ba.Window): ba.internal.new_host_session(self._sessiontype) except Exception: from bastd import mainmenu + ba.print_exception(f'Error running session {self._sessiontype}.') # Drop back into a main menu session. @@ -299,12 +366,14 @@ class PlaylistCustomizeBrowserWindow(ba.Window): def _refresh(self, select_playlist: str | None = None) -> None: from efro.util import asserttype + old_selection = self._selected_playlist_name # If there was no prev selection, look in prefs. if old_selection is None: - old_selection = ba.app.config.get(self._pvars.config_name + - ' Playlist Selection') + old_selection = ba.app.config.get( + self._pvars.config_name + ' Playlist Selection' + ) old_selection_index = self._selected_playlist_index @@ -315,8 +384,10 @@ class PlaylistCustomizeBrowserWindow(ba.Window): items = list(ba.app.config[self._config_name_full].items()) # Make sure everything is unicode now. - items = [(i[0].decode(), i[1]) if not isinstance(i[0], str) else i - for i in items] + items = [ + (i[0].decode(), i[1]) if not isinstance(i[0], str) else i + for i in items + ] items.sort(key=lambda x: asserttype(x[0], str).lower()) @@ -331,12 +402,14 @@ class PlaylistCustomizeBrowserWindow(ba.Window): text=self._get_playlist_display_name(pname), h_align='left', v_align='center', - color=(0.6, 0.6, 0.7, 1.0) if pname == '__default__' else - (0.85, 0.85, 0.85, 1), + color=(0.6, 0.6, 0.7, 1.0) + if pname == '__default__' + else (0.85, 0.85, 0.85, 1), always_highlight=True, on_select_call=ba.Call(self._select, pname, index), on_activate_call=ba.Call(self._edit_button.activate), - selectable=True) + selectable=True, + ) ba.widget(edit=txtw, show_buffer_top=50, show_buffer_bottom=50) # Hitting up from top widget should jump to 'back' @@ -348,22 +421,28 @@ class PlaylistCustomizeBrowserWindow(ba.Window): # Select this one if the user requested it. if select_playlist is not None: if pname == select_playlist: - ba.columnwidget(edit=self._columnwidget, - selected_child=txtw, - visible_child=txtw) + ba.columnwidget( + edit=self._columnwidget, + selected_child=txtw, + visible_child=txtw, + ) else: # Select this one if it was previously selected. # Go by index if there's one. if old_selection_index is not None: if index == old_selection_index: - ba.columnwidget(edit=self._columnwidget, - selected_child=txtw, - visible_child=txtw) + ba.columnwidget( + edit=self._columnwidget, + selected_child=txtw, + visible_child=txtw, + ) else: # Otherwise look by name. if pname == old_selection: - ba.columnwidget(edit=self._columnwidget, - selected_child=txtw, - visible_child=txtw) + ba.columnwidget( + edit=self._columnwidget, + selected_child=txtw, + visible_child=txtw, + ) index += 1 @@ -373,16 +452,19 @@ class PlaylistCustomizeBrowserWindow(ba.Window): # if we want and also lets us pass it to the game (since we reset # the whole python environment that's not actually easy). cfg = ba.app.config - cfg[self._pvars.config_name + - ' Playlist Selection'] = self._selected_playlist_name - cfg[self._pvars.config_name + - ' Playlist Randomize'] = self._do_randomize_val + cfg[ + self._pvars.config_name + ' Playlist Selection' + ] = self._selected_playlist_name + cfg[ + self._pvars.config_name + ' Playlist Randomize' + ] = self._do_randomize_val cfg.commit() def _new_playlist(self) -> None: # pylint: disable=cyclic-import from bastd.ui.playlist.editcontroller import PlaylistEditController from bastd.ui.purchase import PurchaseWindow + if not ba.app.accounts_v1.have_pro_options(): PurchaseWindow(items=['pro']) return @@ -390,9 +472,14 @@ class PlaylistCustomizeBrowserWindow(ba.Window): # Clamp at our max playlist number. if len(ba.app.config[self._config_name_full]) > self._max_playlists: ba.screenmessage( - ba.Lstr(translate=('serverResponses', - 'Max number of playlists reached.')), - color=(1, 0, 0)) + ba.Lstr( + translate=( + 'serverResponses', + 'Max number of playlists reached.', + ) + ), + color=(1, 0, 0), + ) ba.playsound(ba.getsound('error')) return @@ -407,6 +494,7 @@ class PlaylistCustomizeBrowserWindow(ba.Window): # pylint: disable=cyclic-import from bastd.ui.playlist.editcontroller import PlaylistEditController from bastd.ui.purchase import PurchaseWindow + if not ba.app.accounts_v1.have_pro_options(): PurchaseWindow(items=['pro']) return @@ -414,30 +502,34 @@ class PlaylistCustomizeBrowserWindow(ba.Window): return if self._selected_playlist_name == '__default__': ba.playsound(ba.getsound('error')) - ba.screenmessage(ba.Lstr(resource=self._r + - '.cantEditDefaultText')) + ba.screenmessage(ba.Lstr(resource=self._r + '.cantEditDefaultText')) return self._save_playlist_selection() PlaylistEditController( existing_playlist_name=self._selected_playlist_name, - sessiontype=self._sessiontype) + sessiontype=self._sessiontype, + ) ba.containerwidget(edit=self._root_widget, transition='out_left') def _do_delete_playlist(self) -> None: - ba.internal.add_transaction({ - 'type': 'REMOVE_PLAYLIST', - 'playlistType': self._pvars.config_name, - 'playlistName': self._selected_playlist_name - }) + ba.internal.add_transaction( + { + 'type': 'REMOVE_PLAYLIST', + 'playlistType': self._pvars.config_name, + 'playlistName': self._selected_playlist_name, + } + ) ba.internal.run_transactions() ba.playsound(ba.getsound('shieldDown')) # (we don't use len()-1 here because the default list adds one) assert self._selected_playlist_index is not None if self._selected_playlist_index > len( - ba.app.config[self._pvars.config_name + ' Playlists']): + ba.app.config[self._pvars.config_name + ' Playlists'] + ): self._selected_playlist_index = len( - ba.app.config[self._pvars.config_name + ' Playlists']) + ba.app.config[self._pvars.config_name + ' Playlists'] + ) self._refresh() def _import_playlist(self) -> None: @@ -446,14 +538,16 @@ class PlaylistCustomizeBrowserWindow(ba.Window): # Gotta be signed in for this to work. if ba.internal.get_v1_account_state() != 'signed_in': - ba.screenmessage(ba.Lstr(resource='notSignedInErrorText'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr(resource='notSignedInErrorText'), color=(1, 0, 0) + ) ba.playsound(ba.getsound('error')) return - share.SharePlaylistImportWindow(origin_widget=self._import_button, - on_success_callback=ba.WeakCall( - self._on_playlist_import_success)) + share.SharePlaylistImportWindow( + origin_widget=self._import_button, + on_success_callback=ba.WeakCall(self._on_playlist_import_success), + ) def _on_playlist_import_success(self) -> None: self._refresh() @@ -461,10 +555,12 @@ class PlaylistCustomizeBrowserWindow(ba.Window): def _on_share_playlist_response(self, name: str, response: Any) -> None: # pylint: disable=cyclic-import from bastd.ui.playlist import share + if response is None: ba.screenmessage( ba.Lstr(resource='internal.unavailableNoConnectionText'), - color=(1, 0, 0)) + color=(1, 0, 0), + ) ba.playsound(ba.getsound('error')) return share.SharePlaylistResultsWindow(name, response) @@ -472,21 +568,24 @@ class PlaylistCustomizeBrowserWindow(ba.Window): def _share_playlist(self) -> None: # pylint: disable=cyclic-import from bastd.ui.purchase import PurchaseWindow + if not ba.app.accounts_v1.have_pro_options(): PurchaseWindow(items=['pro']) return # Gotta be signed in for this to work. if ba.internal.get_v1_account_state() != 'signed_in': - ba.screenmessage(ba.Lstr(resource='notSignedInErrorText'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr(resource='notSignedInErrorText'), color=(1, 0, 0) + ) ba.playsound(ba.getsound('error')) return if self._selected_playlist_name == '__default__': ba.playsound(ba.getsound('error')) - ba.screenmessage(ba.Lstr(resource=self._r + - '.cantShareDefaultText'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr(resource=self._r + '.cantShareDefaultText'), + color=(1, 0, 0), + ) return if self._selected_playlist_name is None: @@ -497,10 +596,12 @@ class PlaylistCustomizeBrowserWindow(ba.Window): 'type': 'SHARE_PLAYLIST', 'expire_time': time.time() + 5, 'playlistType': self._pvars.config_name, - 'playlistName': self._selected_playlist_name + 'playlistName': self._selected_playlist_name, }, - callback=ba.WeakCall(self._on_share_playlist_response, - self._selected_playlist_name)) + callback=ba.WeakCall( + self._on_share_playlist_response, self._selected_playlist_name + ), + ) ba.internal.run_transactions() ba.screenmessage(ba.Lstr(resource='sharingText')) @@ -508,6 +609,7 @@ class PlaylistCustomizeBrowserWindow(ba.Window): # pylint: disable=cyclic-import from bastd.ui.purchase import PurchaseWindow from bastd.ui.confirm import ConfirmWindow + if not ba.app.accounts_v1.have_pro_options(): PurchaseWindow(items=['pro']) return @@ -517,23 +619,33 @@ class PlaylistCustomizeBrowserWindow(ba.Window): if self._selected_playlist_name == '__default__': ba.playsound(ba.getsound('error')) ba.screenmessage( - ba.Lstr(resource=self._r + '.cantDeleteDefaultText')) + ba.Lstr(resource=self._r + '.cantDeleteDefaultText') + ) else: ConfirmWindow( - ba.Lstr(resource=self._r + '.deleteConfirmText', - subs=[('${LIST}', self._selected_playlist_name)]), - self._do_delete_playlist, 450, 150) + ba.Lstr( + resource=self._r + '.deleteConfirmText', + subs=[('${LIST}', self._selected_playlist_name)], + ), + self._do_delete_playlist, + 450, + 150, + ) def _get_playlist_display_name(self, playlist: str) -> ba.Lstr: if playlist == '__default__': return self._pvars.default_list_name - return playlist if isinstance(playlist, ba.Lstr) else ba.Lstr( - value=playlist) + return ( + playlist + if isinstance(playlist, ba.Lstr) + else ba.Lstr(value=playlist) + ) def _duplicate_playlist(self) -> None: # pylint: disable=too-many-branches # pylint: disable=cyclic-import from bastd.ui.purchase import PurchaseWindow + if not ba.app.accounts_v1.have_pro_options(): PurchaseWindow(items=['pro']) return @@ -544,7 +656,8 @@ class PlaylistCustomizeBrowserWindow(ba.Window): plst = self._pvars.get_default_list_call() else: plst = ba.app.config[self._config_name_full].get( - self._selected_playlist_name) + self._selected_playlist_name + ) if plst is None: ba.playsound(ba.getsound('error')) return @@ -552,9 +665,14 @@ class PlaylistCustomizeBrowserWindow(ba.Window): # clamp at our max playlist number if len(ba.app.config[self._config_name_full]) > self._max_playlists: ba.screenmessage( - ba.Lstr(translate=('serverResponses', - 'Max number of playlists reached.')), - color=(1, 0, 0)) + ba.Lstr( + translate=( + 'serverResponses', + 'Max number of playlists reached.', + ) + ), + color=(1, 0, 0), + ) ba.playsound(ba.getsound('error')) return @@ -565,7 +683,8 @@ class PlaylistCustomizeBrowserWindow(ba.Window): test_index = 1 base_name = self._get_playlist_display_name( - self._selected_playlist_name).evaluate() + self._selected_playlist_name + ).evaluate() # If it looks like a copy, strip digits and spaces off the end. if copy_word in base_name: @@ -582,12 +701,14 @@ class PlaylistCustomizeBrowserWindow(ba.Window): break test_index += 1 - ba.internal.add_transaction({ - 'type': 'ADD_PLAYLIST', - 'playlistType': self._pvars.config_name, - 'playlistName': test_name, - 'playlist': copy.deepcopy(plst) - }) + ba.internal.add_transaction( + { + 'type': 'ADD_PLAYLIST', + 'playlistType': self._pvars.config_name, + 'playlistName': test_name, + 'playlist': copy.deepcopy(plst), + } + ) ba.internal.run_transactions() ba.playsound(ba.getsound('gunCocking')) diff --git a/dist/ba_data/python/bastd/ui/playlist/edit.py b/dist/ba_data/python/bastd/ui/playlist/edit.py index 7e2040b..ed9b319 100644 --- a/dist/ba_data/python/bastd/ui/playlist/edit.py +++ b/dist/ba_data/python/bastd/ui/playlist/edit.py @@ -16,9 +16,11 @@ if TYPE_CHECKING: class PlaylistEditWindow(ba.Window): """Window for editing an individual game playlist.""" - def __init__(self, - editcontroller: PlaylistEditController, - transition: str = 'in_right'): + def __init__( + self, + editcontroller: PlaylistEditController, + transition: str = 'in_right', + ): # pylint: disable=too-many-statements # pylint: disable=too-many-locals prev_selection: str | None @@ -29,24 +31,40 @@ class PlaylistEditWindow(ba.Window): uiscale = ba.app.ui.uiscale self._width = 770 if uiscale is ba.UIScale.SMALL else 670 x_inset = 50 if uiscale is ba.UIScale.SMALL else 0 - self._height = (400 if uiscale is ba.UIScale.SMALL else - 470 if uiscale is ba.UIScale.MEDIUM else 540) + self._height = ( + 400 + if uiscale is ba.UIScale.SMALL + else 470 + if uiscale is ba.UIScale.MEDIUM + else 540 + ) top_extra = 20 if uiscale is ba.UIScale.SMALL else 0 - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height + top_extra), - transition=transition, - scale=(2.0 if uiscale is ba.UIScale.SMALL else - 1.3 if uiscale is ba.UIScale.MEDIUM else 1.0), - stack_offset=(0, -16) if uiscale is ba.UIScale.SMALL else (0, 0))) - cancel_button = ba.buttonwidget(parent=self._root_widget, - position=(35 + x_inset, - self._height - 60), - scale=0.8, - size=(175, 60), - autoselect=True, - label=ba.Lstr(resource='cancelText'), - text_scale=1.2) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height + top_extra), + transition=transition, + scale=( + 2.0 + if uiscale is ba.UIScale.SMALL + else 1.3 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + stack_offset=(0, -16) + if uiscale is ba.UIScale.SMALL + else (0, 0), + ) + ) + cancel_button = ba.buttonwidget( + parent=self._root_widget, + position=(35 + x_inset, self._height - 60), + scale=0.8, + size=(175, 60), + autoselect=True, + label=ba.Lstr(resource='cancelText'), + text_scale=1.2, + ) save_button = btn = ba.buttonwidget( parent=self._root_widget, position=(self._width - (195 + x_inset), self._height - 60), @@ -55,40 +73,48 @@ class PlaylistEditWindow(ba.Window): autoselect=True, left_widget=cancel_button, label=ba.Lstr(resource='saveText'), - text_scale=1.2) + text_scale=1.2, + ) if ba.app.ui.use_toolbars: ba.widget( edit=btn, - right_widget=ba.internal.get_special_widget('party_button')) + right_widget=ba.internal.get_special_widget('party_button'), + ) - ba.widget(edit=cancel_button, - left_widget=cancel_button, - right_widget=save_button) + ba.widget( + edit=cancel_button, + left_widget=cancel_button, + right_widget=save_button, + ) - ba.textwidget(parent=self._root_widget, - position=(-10, self._height - 50), - size=(self._width, 25), - text=ba.Lstr(resource=self._r + '.titleText'), - color=ba.app.ui.title_color, - scale=1.05, - h_align='center', - v_align='center', - maxwidth=270) + ba.textwidget( + parent=self._root_widget, + position=(-10, self._height - 50), + size=(self._width, 25), + text=ba.Lstr(resource=self._r + '.titleText'), + color=ba.app.ui.title_color, + scale=1.05, + h_align='center', + v_align='center', + maxwidth=270, + ) v = self._height - 115.0 self._scroll_width = self._width - (205 + 2 * x_inset) - ba.textwidget(parent=self._root_widget, - text=ba.Lstr(resource=self._r + '.listNameText'), - position=(196 + x_inset, v + 31), - maxwidth=150, - color=(0.8, 0.8, 0.8, 0.5), - size=(0, 0), - scale=0.75, - h_align='right', - v_align='center') + ba.textwidget( + parent=self._root_widget, + text=ba.Lstr(resource=self._r + '.listNameText'), + position=(196 + x_inset, v + 31), + maxwidth=150, + color=(0.8, 0.8, 0.8, 0.5), + size=(0, 0), + scale=0.75, + h_align='right', + v_align='center', + ) self._text_field = ba.textwidget( parent=self._root_widget, @@ -103,7 +129,8 @@ class PlaylistEditWindow(ba.Window): description=ba.Lstr(resource=self._r + '.listNameText'), editable=True, padding=4, - on_return_press_call=self._save_press_with_sound) + on_return_press_call=self._save_press_with_sound, + ) ba.widget(edit=cancel_button, down_widget=self._text_field) self._list_widgets: list[ba.Widget] = [] @@ -117,8 +144,13 @@ class PlaylistEditWindow(ba.Window): v -= 2.0 v += 63 - scl = (1.03 if uiscale is ba.UIScale.SMALL else - 1.36 if uiscale is ba.UIScale.MEDIUM else 1.74) + scl = ( + 1.03 + if uiscale is ba.UIScale.SMALL + else 1.36 + if uiscale is ba.UIScale.MEDIUM + else 1.74 + ) v -= 63.0 * scl add_game_button = ba.buttonwidget( @@ -132,7 +164,8 @@ class PlaylistEditWindow(ba.Window): color=b_color, textcolor=b_textcolor, text_scale=0.8, - label=ba.Lstr(resource=self._r + '.addGameText')) + label=ba.Lstr(resource=self._r + '.addGameText'), + ) ba.widget(edit=add_game_button, up_widget=self._text_field) v -= 63.0 * scl @@ -147,43 +180,49 @@ class PlaylistEditWindow(ba.Window): color=b_color, textcolor=b_textcolor, text_scale=0.8, - label=ba.Lstr(resource=self._r + '.editGameText')) + label=ba.Lstr(resource=self._r + '.editGameText'), + ) v -= 63.0 * scl - remove_game_button = ba.buttonwidget(parent=self._root_widget, - position=(h, v), - size=(110, 61.0 * scl), - text_scale=0.8, - on_activate_call=self._remove, - autoselect=True, - button_type='square', - color=b_color, - textcolor=b_textcolor, - label=ba.Lstr(resource=self._r + - '.removeGameText')) + remove_game_button = ba.buttonwidget( + parent=self._root_widget, + position=(h, v), + size=(110, 61.0 * scl), + text_scale=0.8, + on_activate_call=self._remove, + autoselect=True, + button_type='square', + color=b_color, + textcolor=b_textcolor, + label=ba.Lstr(resource=self._r + '.removeGameText'), + ) v -= 40 h += 9 - ba.buttonwidget(parent=self._root_widget, - position=(h, v), - size=(42, 35), - on_activate_call=self._move_up, - label=ba.charstr(ba.SpecialChar.UP_ARROW), - button_type='square', - color=b_color, - textcolor=b_textcolor, - autoselect=True, - repeat=True) + ba.buttonwidget( + parent=self._root_widget, + position=(h, v), + size=(42, 35), + on_activate_call=self._move_up, + label=ba.charstr(ba.SpecialChar.UP_ARROW), + button_type='square', + color=b_color, + textcolor=b_textcolor, + autoselect=True, + repeat=True, + ) h += 52 - ba.buttonwidget(parent=self._root_widget, - position=(h, v), - size=(42, 35), - on_activate_call=self._move_down, - autoselect=True, - button_type='square', - color=b_color, - textcolor=b_textcolor, - label=ba.charstr(ba.SpecialChar.DOWN_ARROW), - repeat=True) + ba.buttonwidget( + parent=self._root_widget, + position=(h, v), + size=(42, 35), + on_activate_call=self._move_down, + autoselect=True, + button_type='square', + color=b_color, + textcolor=b_textcolor, + label=ba.charstr(ba.SpecialChar.DOWN_ARROW), + repeat=True, + ) v = self._height - 100 scroll_height = self._height - 155 @@ -192,77 +231,98 @@ class PlaylistEditWindow(ba.Window): position=(160 + x_inset, v - scroll_height), highlight=False, on_select_call=ba.Call(self._set_ui_selection, 'gameList'), - size=(self._scroll_width, (scroll_height - 15))) - ba.widget(edit=scrollwidget, - left_widget=add_game_button, - right_widget=scrollwidget) - self._columnwidget = ba.columnwidget(parent=scrollwidget, - border=2, - margin=0) + size=(self._scroll_width, (scroll_height - 15)), + ) + ba.widget( + edit=scrollwidget, + left_widget=add_game_button, + right_widget=scrollwidget, + ) + self._columnwidget = ba.columnwidget( + parent=scrollwidget, border=2, margin=0 + ) ba.widget(edit=self._columnwidget, up_widget=self._text_field) for button in [add_game_button, edit_game_button, remove_game_button]: - ba.widget(edit=button, - left_widget=button, - right_widget=scrollwidget) + ba.widget( + edit=button, left_widget=button, right_widget=scrollwidget + ) self._refresh() ba.buttonwidget(edit=cancel_button, on_activate_call=self._cancel) - ba.containerwidget(edit=self._root_widget, - cancel_button=cancel_button, - selected_child=scrollwidget) + ba.containerwidget( + edit=self._root_widget, + cancel_button=cancel_button, + selected_child=scrollwidget, + ) ba.buttonwidget(edit=save_button, on_activate_call=self._save_press) ba.containerwidget(edit=self._root_widget, start_button=save_button) if prev_selection == 'add_button': - ba.containerwidget(edit=self._root_widget, - selected_child=add_game_button) + ba.containerwidget( + edit=self._root_widget, selected_child=add_game_button + ) elif prev_selection == 'editButton': - ba.containerwidget(edit=self._root_widget, - selected_child=edit_game_button) + ba.containerwidget( + edit=self._root_widget, selected_child=edit_game_button + ) elif prev_selection == 'gameList': - ba.containerwidget(edit=self._root_widget, - selected_child=scrollwidget) + ba.containerwidget( + edit=self._root_widget, selected_child=scrollwidget + ) def _set_ui_selection(self, selection: str) -> None: self._editcontroller.set_edit_ui_selection(selection) def _cancel(self) -> None: from bastd.ui.playlist.customizebrowser import ( - PlaylistCustomizeBrowserWindow) + PlaylistCustomizeBrowserWindow, + ) + ba.playsound(ba.getsound('powerdown01')) ba.containerwidget(edit=self._root_widget, transition='out_right') ba.app.ui.set_main_menu_window( PlaylistCustomizeBrowserWindow( transition='in_left', sessiontype=self._editcontroller.get_session_type(), - select_playlist=self._editcontroller. - get_existing_playlist_name()).get_root_widget()) + select_playlist=( + self._editcontroller.get_existing_playlist_name() + ), + ).get_root_widget() + ) def _add(self) -> None: # Store list name then tell the session to perform an add. self._editcontroller.setname( - cast(str, ba.textwidget(query=self._text_field))) + cast(str, ba.textwidget(query=self._text_field)) + ) self._editcontroller.add_game_pressed() def _edit(self) -> None: # Store list name then tell the session to perform an add. self._editcontroller.setname( - cast(str, ba.textwidget(query=self._text_field))) + cast(str, ba.textwidget(query=self._text_field)) + ) self._editcontroller.edit_game_pressed() def _save_press(self) -> None: from bastd.ui.playlist.customizebrowser import ( - PlaylistCustomizeBrowserWindow) + PlaylistCustomizeBrowserWindow, + ) + new_name = cast(str, ba.textwidget(query=self._text_field)) - if (new_name != self._editcontroller.get_existing_playlist_name() - and new_name - in ba.app.config[self._editcontroller.get_config_name() + - ' Playlists']): + if ( + new_name != self._editcontroller.get_existing_playlist_name() + and new_name + in ba.app.config[ + self._editcontroller.get_config_name() + ' Playlists' + ] + ): ba.screenmessage( - ba.Lstr(resource=self._r + '.cantSaveAlreadyExistsText')) + ba.Lstr(resource=self._r + '.cantSaveAlreadyExistsText') + ) ba.playsound(ba.getsound('error')) return if not new_name: @@ -270,7 +330,8 @@ class PlaylistEditWindow(ba.Window): return if not self._editcontroller.get_playlist(): ba.screenmessage( - ba.Lstr(resource=self._r + '.cantSaveEmptyListText')) + ba.Lstr(resource=self._r + '.cantSaveEmptyListText') + ) ba.playsound(ba.getsound('error')) return @@ -278,27 +339,31 @@ class PlaylistEditWindow(ba.Window): # using its exact name to avoid confusion. if new_name == self._editcontroller.get_default_list_name().evaluate(): ba.screenmessage( - ba.Lstr(resource=self._r + '.cantOverwriteDefaultText')) + ba.Lstr(resource=self._r + '.cantOverwriteDefaultText') + ) ba.playsound(ba.getsound('error')) return # If we had an old one, delete it. if self._editcontroller.get_existing_playlist_name() is not None: - ba.internal.add_transaction({ - 'type': - 'REMOVE_PLAYLIST', - 'playlistType': - self._editcontroller.get_config_name(), - 'playlistName': - self._editcontroller.get_existing_playlist_name() - }) + ba.internal.add_transaction( + { + 'type': 'REMOVE_PLAYLIST', + 'playlistType': self._editcontroller.get_config_name(), + 'playlistName': ( + self._editcontroller.get_existing_playlist_name() + ), + } + ) - ba.internal.add_transaction({ - 'type': 'ADD_PLAYLIST', - 'playlistType': self._editcontroller.get_config_name(), - 'playlistName': new_name, - 'playlist': self._editcontroller.get_playlist() - }) + ba.internal.add_transaction( + { + 'type': 'ADD_PLAYLIST', + 'playlistType': self._editcontroller.get_config_name(), + 'playlistName': new_name, + 'playlist': self._editcontroller.get_playlist(), + } + ) ba.internal.run_transactions() ba.containerwidget(edit=self._root_widget, transition='out_right') @@ -307,7 +372,9 @@ class PlaylistEditWindow(ba.Window): PlaylistCustomizeBrowserWindow( transition='in_left', sessiontype=self._editcontroller.get_session_type(), - select_playlist=new_name).get_root_widget()) + select_playlist=new_name, + ).get_root_widget() + ) def _save_press_with_sound(self) -> None: ba.playsound(ba.getsound('swish')) @@ -334,17 +401,19 @@ class PlaylistEditWindow(ba.Window): ba.print_exception() desc = "(invalid: '" + pentry['type'] + "')" - txtw = ba.textwidget(parent=self._columnwidget, - size=(self._width - 80, 30), - on_select_call=ba.Call(self._select, index), - always_highlight=True, - color=(0.8, 0.8, 0.8, 1.0), - padding=0, - maxwidth=self._scroll_width * 0.93, - text=desc, - on_activate_call=self._edit_button.activate, - v_align='center', - selectable=True) + txtw = ba.textwidget( + parent=self._columnwidget, + size=(self._width - 80, 30), + on_select_call=ba.Call(self._select, index), + always_highlight=True, + color=(0.8, 0.8, 0.8, 1.0), + padding=0, + maxwidth=self._scroll_width * 0.93, + text=desc, + on_activate_call=self._edit_button.activate, + v_align='center', + selectable=True, + ) ba.widget(edit=txtw, show_buffer_top=50, show_buffer_bottom=50) # Wanna be able to jump up to the text field from the top one. @@ -352,9 +421,11 @@ class PlaylistEditWindow(ba.Window): ba.widget(edit=txtw, up_widget=self._text_field) self._list_widgets.append(txtw) if old_selection_index == index: - ba.columnwidget(edit=self._columnwidget, - selected_child=txtw, - visible_child=txtw) + ba.columnwidget( + edit=self._columnwidget, + selected_child=txtw, + visible_child=txtw, + ) def _move_down(self) -> None: playlist = self._editcontroller.get_playlist() @@ -375,7 +446,7 @@ class PlaylistEditWindow(ba.Window): if index < 1: return tmp = playlist[index] - playlist[index] = (playlist[index - 1]) + playlist[index] = playlist[index - 1] playlist[index - 1] = tmp index -= 1 self._editcontroller.set_playlist(playlist) diff --git a/dist/ba_data/python/bastd/ui/playlist/editcontroller.py b/dist/ba_data/python/bastd/ui/playlist/editcontroller.py index 664f649..9f978a3 100644 --- a/dist/ba_data/python/bastd/ui/playlist/editcontroller.py +++ b/dist/ba_data/python/bastd/ui/playlist/editcontroller.py @@ -16,12 +16,14 @@ if TYPE_CHECKING: class PlaylistEditController: """Coordinates various UIs involved in playlist editing.""" - def __init__(self, - sessiontype: type[ba.Session], - existing_playlist_name: str | None = None, - transition: str = 'in_right', - playlist: list[dict[str, Any]] | None = None, - playlist_name: str | None = None): + def __init__( + self, + sessiontype: type[ba.Session], + existing_playlist_name: str | None = None, + transition: str = 'in_right', + playlist: list[dict[str, Any]] | None = None, + playlist_name: str | None = None, + ): from ba.internal import preload_map_preview_media, filter_playlist from bastd.ui.playlist import PlaylistTypeVars from bastd.ui.playlist.edit import PlaylistEditWindow @@ -49,11 +51,13 @@ class PlaylistEditController: # Filter out invalid games. self._playlist = filter_playlist( - appconfig[self._pvars.config_name + - ' Playlists'][existing_playlist_name], + appconfig[self._pvars.config_name + ' Playlists'][ + existing_playlist_name + ], sessiontype=sessiontype, remove_unowned=False, - name=existing_playlist_name) + name=existing_playlist_name, + ) self._edit_ui_selection = None else: if playlist is not None: @@ -68,10 +72,13 @@ class PlaylistEditController: i = 1 while True: self._name = ( - self._pvars.default_new_list_name.evaluate() + - ((' ' + str(i)) if i > 1 else '')) - if self._name not in appconfig[self._pvars.config_name + - ' Playlists']: + self._pvars.default_new_list_name.evaluate() + + ((' ' + str(i)) if i > 1 else '') + ) + if ( + self._name + not in appconfig[self._pvars.config_name + ' Playlists'] + ): break i += 1 @@ -80,8 +87,10 @@ class PlaylistEditController: self._edit_ui_selection = 'add_button' ba.app.ui.set_main_menu_window( - PlaylistEditWindow(editcontroller=self, - transition=transition).get_root_widget()) + PlaylistEditWindow( + editcontroller=self, transition=transition + ).get_root_widget() + ) def get_config_name(self) -> str: """(internal)""" @@ -134,35 +143,46 @@ class PlaylistEditController: def add_game_pressed(self) -> None: """(internal)""" from bastd.ui.playlist.addgame import PlaylistAddGameWindow + ba.app.ui.clear_main_menu_window(transition='out_left') ba.app.ui.set_main_menu_window( - PlaylistAddGameWindow(editcontroller=self).get_root_widget()) + PlaylistAddGameWindow(editcontroller=self).get_root_widget() + ) def edit_game_pressed(self) -> None: """Should be called by supplemental UIs when a game is to be edited.""" from ba.internal import getclass + if not self._playlist: return - self._show_edit_ui(gametype=getclass( - self._playlist[self._selected_index]['type'], - subclassof=ba.GameActivity), - settings=self._playlist[self._selected_index]) + self._show_edit_ui( + gametype=getclass( + self._playlist[self._selected_index]['type'], + subclassof=ba.GameActivity, + ), + settings=self._playlist[self._selected_index], + ) def add_game_cancelled(self) -> None: """(internal)""" from bastd.ui.playlist.edit import PlaylistEditWindow + ba.app.ui.clear_main_menu_window(transition='out_right') ba.app.ui.set_main_menu_window( - PlaylistEditWindow(editcontroller=self, - transition='in_left').get_root_widget()) + PlaylistEditWindow( + editcontroller=self, transition='in_left' + ).get_root_widget() + ) - def _show_edit_ui(self, gametype: type[ba.GameActivity], - settings: dict[str, Any] | None) -> None: - self._editing_game = (settings is not None) + def _show_edit_ui( + self, gametype: type[ba.GameActivity], settings: dict[str, Any] | None + ) -> None: + self._editing_game = settings is not None self._editing_game_type = gametype assert self._sessiontype is not None - gametype.create_settings_ui(self._sessiontype, copy.deepcopy(settings), - self._edit_game_done) + gametype.create_settings_ui( + self._sessiontype, copy.deepcopy(settings), self._edit_game_done + ) def add_game_type_selected(self, gametype: type[ba.GameActivity]) -> None: """(internal)""" @@ -172,22 +192,26 @@ class PlaylistEditController: from bastd.ui.playlist.edit import PlaylistEditWindow from bastd.ui.playlist.addgame import PlaylistAddGameWindow from ba.internal import get_type_name + if config is None: # If we were editing, go back to our list. if self._editing_game: ba.playsound(ba.getsound('powerdown01')) ba.app.ui.clear_main_menu_window(transition='out_right') ba.app.ui.set_main_menu_window( - PlaylistEditWindow(editcontroller=self, - transition='in_left').get_root_widget()) + PlaylistEditWindow( + editcontroller=self, transition='in_left' + ).get_root_widget() + ) # Otherwise we were adding; go back to the add type choice list. else: ba.app.ui.clear_main_menu_window(transition='out_right') ba.app.ui.set_main_menu_window( PlaylistAddGameWindow( - editcontroller=self, - transition='in_left').get_root_widget()) + editcontroller=self, transition='in_left' + ).get_root_widget() + ) else: # Make sure type is in there. assert self._editing_game_type is not None @@ -197,13 +221,16 @@ class PlaylistEditController: self._playlist[self._selected_index] = copy.deepcopy(config) else: # Add a new entry to the playlist. - insert_index = min(len(self._playlist), - self._selected_index + 1) + insert_index = min( + len(self._playlist), self._selected_index + 1 + ) self._playlist.insert(insert_index, copy.deepcopy(config)) self._selected_index = insert_index ba.playsound(ba.getsound('gunCocking')) ba.app.ui.clear_main_menu_window(transition='out_right') ba.app.ui.set_main_menu_window( - PlaylistEditWindow(editcontroller=self, - transition='in_left').get_root_widget()) + PlaylistEditWindow( + editcontroller=self, transition='in_left' + ).get_root_widget() + ) diff --git a/dist/ba_data/python/bastd/ui/playlist/editgame.py b/dist/ba_data/python/bastd/ui/playlist/editgame.py index ece7304..ee3a2cd 100644 --- a/dist/ba_data/python/bastd/ui/playlist/editgame.py +++ b/dist/ba_data/python/bastd/ui/playlist/editgame.py @@ -18,19 +18,26 @@ if TYPE_CHECKING: class PlaylistEditGameWindow(ba.Window): """Window for editing a game config.""" - def __init__(self, - gametype: type[ba.GameActivity], - sessiontype: type[ba.Session], - config: dict[str, Any] | None, - completion_call: Callable[[dict[str, Any] | None], Any], - default_selection: str | None = None, - transition: str = 'in_right', - edit_info: dict[str, Any] | None = None): + def __init__( + self, + gametype: type[ba.GameActivity], + sessiontype: type[ba.Session], + config: dict[str, Any] | None, + completion_call: Callable[[dict[str, Any] | None], Any], + default_selection: str | None = None, + transition: str = 'in_right', + edit_info: dict[str, Any] | None = None, + ): # pylint: disable=too-many-branches # pylint: disable=too-many-statements # pylint: disable=too-many-locals - from ba.internal import (get_unowned_maps, get_filtered_map_name, - get_map_class, get_map_display_string) + from ba.internal import ( + get_unowned_maps, + get_filtered_map_name, + get_map_class, + get_map_display_string, + ) + self._gametype = gametype self._sessiontype = sessiontype @@ -68,14 +75,18 @@ class PlaylistEditGameWindow(ba.Window): else: self._map = valid_maps[random.randrange(len(valid_maps))] - is_add = (self._edit_info['editType'] == 'add') + is_add = self._edit_info['editType'] == 'add' # If there's a valid map name in the existing config, use that. try: - if (config is not None and 'settings' in config - and 'map' in config['settings']): + if ( + config is not None + and 'settings' in config + and 'map' in config['settings'] + ): filtered_map_name = get_filtered_map_name( - config['settings']['map']) + config['settings']['map'] + ) if filtered_map_name in valid_maps: self._map = filtered_map_name except Exception: @@ -91,36 +102,53 @@ class PlaylistEditGameWindow(ba.Window): uiscale = ba.app.ui.uiscale width = 720 if uiscale is ba.UIScale.SMALL else 620 x_inset = 50 if uiscale is ba.UIScale.SMALL else 0 - height = (365 if uiscale is ba.UIScale.SMALL else - 460 if uiscale is ba.UIScale.MEDIUM else 550) + height = ( + 365 + if uiscale is ba.UIScale.SMALL + else 460 + if uiscale is ba.UIScale.MEDIUM + else 550 + ) spacing = 52 y_extra = 15 y_extra2 = 21 - map_tex_name = (get_map_class(self._map).get_preview_texture_name()) + map_tex_name = get_map_class(self._map).get_preview_texture_name() if map_tex_name is None: raise Exception('no map preview tex found for' + self._map) map_tex = ba.gettexture(map_tex_name) top_extra = 20 if uiscale is ba.UIScale.SMALL else 0 - super().__init__(root_widget=ba.containerwidget( - size=(width, height + top_extra), - transition=transition, - scale=(2.19 if uiscale is ba.UIScale.SMALL else - 1.35 if uiscale is ba.UIScale.MEDIUM else 1.0), - stack_offset=(0, -17) if uiscale is ba.UIScale.SMALL else (0, 0))) + super().__init__( + root_widget=ba.containerwidget( + size=(width, height + top_extra), + transition=transition, + scale=( + 2.19 + if uiscale is ba.UIScale.SMALL + else 1.35 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + stack_offset=(0, -17) + if uiscale is ba.UIScale.SMALL + else (0, 0), + ) + ) btn = ba.buttonwidget( parent=self._root_widget, position=(45 + x_inset, height - 82 + y_extra2), size=(180, 70) if is_add else (180, 65), - label=ba.Lstr(resource='backText') if is_add else ba.Lstr( - resource='cancelText'), + label=ba.Lstr(resource='backText') + if is_add + else ba.Lstr(resource='cancelText'), button_type='back' if is_add else None, autoselect=True, scale=0.75, text_scale=1.3, - on_activate_call=ba.Call(self._cancel)) + on_activate_call=ba.Call(self._cancel), + ) ba.containerwidget(edit=self._root_widget, cancel_button=btn) add_button = ba.buttonwidget( @@ -129,23 +157,26 @@ class PlaylistEditGameWindow(ba.Window): size=(200, 65), scale=0.75, text_scale=1.3, - label=ba.Lstr(resource=self._r + - '.addGameText') if is_add else ba.Lstr( - resource='doneText')) + label=ba.Lstr(resource=self._r + '.addGameText') + if is_add + else ba.Lstr(resource='doneText'), + ) if ba.app.ui.use_toolbars: pbtn = ba.internal.get_special_widget('party_button') ba.widget(edit=add_button, right_widget=pbtn, up_widget=pbtn) - ba.textwidget(parent=self._root_widget, - position=(-8, height - 70 + y_extra2), - size=(width, 25), - text=gametype.get_display_string(), - color=ba.app.ui.title_color, - maxwidth=235, - scale=1.1, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(-8, height - 70 + y_extra2), + size=(width, 25), + text=gametype.get_display_string(), + color=ba.app.ui.title_color, + maxwidth=235, + scale=1.1, + h_align='center', + v_align='center', + ) map_height = 100 @@ -155,21 +186,23 @@ class PlaylistEditGameWindow(ba.Window): scroll_height += spacing * len(self._settings_defs) scroll_width = width - (86 + 2 * x_inset) - self._scrollwidget = ba.scrollwidget(parent=self._root_widget, - position=(44 + x_inset, - 35 + y_extra), - size=(scroll_width, height - 116), - highlight=False, - claims_left_right=True, - claims_tab=True, - selection_loops_to_parent=True) - self._subcontainer = ba.containerwidget(parent=self._scrollwidget, - size=(scroll_width, - scroll_height), - background=False, - claims_left_right=True, - claims_tab=True, - selection_loops_to_parent=True) + self._scrollwidget = ba.scrollwidget( + parent=self._root_widget, + position=(44 + x_inset, 35 + y_extra), + size=(scroll_width, height - 116), + highlight=False, + claims_left_right=True, + claims_tab=True, + selection_loops_to_parent=True, + ) + self._subcontainer = ba.containerwidget( + parent=self._scrollwidget, + size=(scroll_width, scroll_height), + background=False, + claims_left_right=True, + claims_tab=True, + selection_loops_to_parent=True, + ) v = scroll_height - 5 h = -40 @@ -179,14 +212,16 @@ class PlaylistEditGameWindow(ba.Window): widget_column: list[list[ba.Widget]] = [] # Map select button. - ba.textwidget(parent=self._subcontainer, - position=(h + 49, v - 63), - size=(100, 30), - maxwidth=110, - text=ba.Lstr(resource='mapText'), - h_align='left', - color=(0.8, 0.8, 0.8, 1.0), - v_align='center') + ba.textwidget( + parent=self._subcontainer, + position=(h + 49, v - 63), + size=(100, 30), + maxwidth=110, + text=ba.Lstr(resource='mapText'), + h_align='left', + color=(0.8, 0.8, 0.8, 1.0), + v_align='center', + ) ba.imagewidget( parent=self._subcontainer, @@ -195,27 +230,31 @@ class PlaylistEditGameWindow(ba.Window): texture=map_tex, model_opaque=ba.getmodel('level_select_button_opaque'), model_transparent=ba.getmodel('level_select_button_transparent'), - mask_texture=ba.gettexture('mapPreviewMask')) + mask_texture=ba.gettexture('mapPreviewMask'), + ) map_button = btn = ba.buttonwidget( parent=self._subcontainer, size=(140, 60), position=(h + 448, v - 72), on_activate_call=ba.Call(self._select_map), scale=0.7, - label=ba.Lstr(resource='mapSelectText')) + label=ba.Lstr(resource='mapSelectText'), + ) widget_column.append([btn]) - ba.textwidget(parent=self._subcontainer, - position=(h + 363 - 123, v - 114), - size=(100, 30), - flatness=1.0, - shadow=1.0, - scale=0.55, - maxwidth=256 * 0.7 * 0.8, - text=get_map_display_string(self._map), - h_align='center', - color=(0.6, 1.0, 0.6, 1.0), - v_align='center') + ba.textwidget( + parent=self._subcontainer, + position=(h + 363 - 123, v - 114), + size=(100, 30), + flatness=1.0, + shadow=1.0, + scale=0.55, + maxwidth=256 * 0.7 * 0.8, + text=get_map_display_string(self._map), + h_align='center', + color=(0.6, 1.0, 0.6, 1.0), + v_align='center', + ) v -= map_height for setting in self._settings_defs: @@ -225,8 +264,11 @@ class PlaylistEditGameWindow(ba.Window): # Now, if there's an existing value for it in the config, # override with that. try: - if (config is not None and 'settings' in config - and setting.name in config['settings']): + if ( + config is not None + and 'settings' in config + and setting.name in config['settings'] + ): value = value_type(config['settings'][setting.name]) except Exception: ba.print_exception() @@ -244,20 +286,26 @@ class PlaylistEditGameWindow(ba.Window): for choice in setting.choices: if len(choice) != 2: raise ValueError( - "Expected 2-member tuples for 'choices'; got: " + - repr(choice)) + "Expected 2-member tuples for 'choices'; got: " + + repr(choice) + ) if not isinstance(choice[0], str): raise TypeError( 'First value for choice tuple must be a str; got: ' - + repr(choice)) + + repr(choice) + ) if not isinstance(choice[1], value_type): raise TypeError( 'Choice type does not match default value; choice:' - + repr(choice) + '; setting:' + repr(setting)) + + repr(choice) + + '; setting:' + + repr(setting) + ) if value_type not in (int, float): raise TypeError( 'Choice type setting must have int or float default; ' - 'got: ' + repr(setting)) + 'got: ' + repr(setting) + ) # Start at the choice corresponding to the default if possible. self._choice_selections[setting.name] = 0 @@ -267,44 +315,54 @@ class PlaylistEditGameWindow(ba.Window): break v -= spacing - ba.textwidget(parent=self._subcontainer, - position=(h + 50, v), - size=(100, 30), - maxwidth=mw1, - text=name_translated, - h_align='left', - color=(0.8, 0.8, 0.8, 1.0), - v_align='center') + ba.textwidget( + parent=self._subcontainer, + position=(h + 50, v), + size=(100, 30), + maxwidth=mw1, + text=name_translated, + h_align='left', + color=(0.8, 0.8, 0.8, 1.0), + v_align='center', + ) txt = ba.textwidget( parent=self._subcontainer, position=(h + 509 - 95, v), size=(0, 28), - text=self._get_localized_setting_name(setting.choices[ - self._choice_selections[setting.name]][0]), + text=self._get_localized_setting_name( + setting.choices[self._choice_selections[setting.name]][ + 0 + ] + ), editable=False, color=(0.6, 1.0, 0.6, 1.0), maxwidth=mw2, h_align='right', v_align='center', - padding=2) - btn1 = ba.buttonwidget(parent=self._subcontainer, - position=(h + 509 - 50 - 1, v), - size=(28, 28), - label='<', - autoselect=True, - on_activate_call=ba.Call( - self._choice_inc, setting.name, txt, - setting, -1), - repeat=True) - btn2 = ba.buttonwidget(parent=self._subcontainer, - position=(h + 509 + 5, v), - size=(28, 28), - label='>', - autoselect=True, - on_activate_call=ba.Call( - self._choice_inc, setting.name, txt, - setting, 1), - repeat=True) + padding=2, + ) + btn1 = ba.buttonwidget( + parent=self._subcontainer, + position=(h + 509 - 50 - 1, v), + size=(28, 28), + label='<', + autoselect=True, + on_activate_call=ba.Call( + self._choice_inc, setting.name, txt, setting, -1 + ), + repeat=True, + ) + btn2 = ba.buttonwidget( + parent=self._subcontainer, + position=(h + 509 + 5, v), + size=(28, 28), + label='>', + autoselect=True, + on_activate_call=ba.Call( + self._choice_inc, setting.name, txt, setting, 1 + ), + repeat=True, + ) widget_column.append([btn1, btn2]) elif isinstance(setting, (ba.IntSetting, ba.FloatSetting)): @@ -312,78 +370,102 @@ class PlaylistEditGameWindow(ba.Window): min_value = setting.min_value max_value = setting.max_value increment = setting.increment - ba.textwidget(parent=self._subcontainer, - position=(h + 50, v), - size=(100, 30), - text=name_translated, - h_align='left', - color=(0.8, 0.8, 0.8, 1.0), - v_align='center', - maxwidth=mw1) - txt = ba.textwidget(parent=self._subcontainer, - position=(h + 509 - 95, v), - size=(0, 28), - text=str(value), - editable=False, - color=(0.6, 1.0, 0.6, 1.0), - maxwidth=mw2, - h_align='right', - v_align='center', - padding=2) - btn1 = ba.buttonwidget(parent=self._subcontainer, - position=(h + 509 - 50 - 1, v), - size=(28, 28), - label='-', - autoselect=True, - on_activate_call=ba.Call( - self._inc, txt, min_value, - max_value, -increment, value_type, - setting.name), - repeat=True) - btn2 = ba.buttonwidget(parent=self._subcontainer, - position=(h + 509 + 5, v), - size=(28, 28), - label='+', - autoselect=True, - on_activate_call=ba.Call( - self._inc, txt, min_value, - max_value, increment, value_type, - setting.name), - repeat=True) - widget_column.append([btn1, btn2]) - - elif value_type == bool: - v -= spacing - ba.textwidget(parent=self._subcontainer, - position=(h + 50, v), - size=(100, 30), - text=name_translated, - h_align='left', - color=(0.8, 0.8, 0.8, 1.0), - v_align='center', - maxwidth=mw1) + ba.textwidget( + parent=self._subcontainer, + position=(h + 50, v), + size=(100, 30), + text=name_translated, + h_align='left', + color=(0.8, 0.8, 0.8, 1.0), + v_align='center', + maxwidth=mw1, + ) txt = ba.textwidget( parent=self._subcontainer, position=(h + 509 - 95, v), size=(0, 28), - text=ba.Lstr(resource='onText') if value else ba.Lstr( - resource='offText'), + text=str(value), editable=False, color=(0.6, 1.0, 0.6, 1.0), maxwidth=mw2, h_align='right', v_align='center', - padding=2) - cbw = ba.checkboxwidget(parent=self._subcontainer, - text='', - position=(h + 505 - 50 - 5, v - 2), - size=(200, 30), - autoselect=True, - textcolor=(0.8, 0.8, 0.8), - value=value, - on_value_change_call=ba.Call( - self._check_value_change, - setting.name, txt)) + padding=2, + ) + btn1 = ba.buttonwidget( + parent=self._subcontainer, + position=(h + 509 - 50 - 1, v), + size=(28, 28), + label='-', + autoselect=True, + on_activate_call=ba.Call( + self._inc, + txt, + min_value, + max_value, + -increment, + value_type, + setting.name, + ), + repeat=True, + ) + btn2 = ba.buttonwidget( + parent=self._subcontainer, + position=(h + 509 + 5, v), + size=(28, 28), + label='+', + autoselect=True, + on_activate_call=ba.Call( + self._inc, + txt, + min_value, + max_value, + increment, + value_type, + setting.name, + ), + repeat=True, + ) + widget_column.append([btn1, btn2]) + + elif value_type == bool: + v -= spacing + ba.textwidget( + parent=self._subcontainer, + position=(h + 50, v), + size=(100, 30), + text=name_translated, + h_align='left', + color=(0.8, 0.8, 0.8, 1.0), + v_align='center', + maxwidth=mw1, + ) + txt = ba.textwidget( + parent=self._subcontainer, + position=(h + 509 - 95, v), + size=(0, 28), + text=ba.Lstr(resource='onText') + if value + else ba.Lstr(resource='offText'), + editable=False, + color=(0.6, 1.0, 0.6, 1.0), + maxwidth=mw2, + h_align='right', + v_align='center', + padding=2, + ) + cbw = ba.checkboxwidget( + parent=self._subcontainer, + text='', + position=(h + 505 - 50 - 5, v - 2), + size=(200, 30), + autoselect=True, + textcolor=(0.8, 0.8, 0.8), + value=value, + on_value_change_call=ba.Call( + self._check_value_change, setting.name, txt + ), + ) widget_column.append([cbw]) else: @@ -405,18 +487,23 @@ class PlaylistEditGameWindow(ba.Window): prev_widgets = cwdg except Exception: ba.print_exception( - 'Error wiring up game-settings-select widget column.') + 'Error wiring up game-settings-select widget column.' + ) ba.buttonwidget(edit=add_button, on_activate_call=ba.Call(self._add)) - ba.containerwidget(edit=self._root_widget, - selected_child=add_button, - start_button=add_button) + ba.containerwidget( + edit=self._root_widget, + selected_child=add_button, + start_button=add_button, + ) if default_selection == 'map': - ba.containerwidget(edit=self._root_widget, - selected_child=self._scrollwidget) - ba.containerwidget(edit=self._subcontainer, - selected_child=map_button) + ba.containerwidget( + edit=self._root_widget, selected_child=self._scrollwidget + ) + ba.containerwidget( + edit=self._subcontainer, selected_child=map_button + ) def _get_localized_setting_name(self, name: str) -> ba.Lstr: return ba.Lstr(translate=('settingNames', name)) @@ -428,34 +515,53 @@ class PlaylistEditGameWindow(ba.Window): # Replace ourself with the map-select UI. ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.ui.set_main_menu_window( - PlaylistMapSelectWindow(self._gametype, self._sessiontype, - copy.deepcopy(self._getconfig()), - self._edit_info, - self._completion_call).get_root_widget()) + PlaylistMapSelectWindow( + self._gametype, + self._sessiontype, + copy.deepcopy(self._getconfig()), + self._edit_info, + self._completion_call, + ).get_root_widget() + ) - def _choice_inc(self, setting_name: str, widget: ba.Widget, - setting: ba.ChoiceSetting, increment: int) -> None: + def _choice_inc( + self, + setting_name: str, + widget: ba.Widget, + setting: ba.ChoiceSetting, + increment: int, + ) -> None: choices = setting.choices if increment > 0: self._choice_selections[setting_name] = min( - len(choices) - 1, self._choice_selections[setting_name] + 1) + len(choices) - 1, self._choice_selections[setting_name] + 1 + ) else: self._choice_selections[setting_name] = max( - 0, self._choice_selections[setting_name] - 1) - ba.textwidget(edit=widget, - text=self._get_localized_setting_name( - choices[self._choice_selections[setting_name]][0])) + 0, self._choice_selections[setting_name] - 1 + ) + ba.textwidget( + edit=widget, + text=self._get_localized_setting_name( + choices[self._choice_selections[setting_name]][0] + ), + ) self._settings[setting_name] = choices[ - self._choice_selections[setting_name]][1] + self._choice_selections[setting_name] + ][1] def _cancel(self) -> None: self._completion_call(None) - def _check_value_change(self, setting_name: str, widget: ba.Widget, - value: int) -> None: - ba.textwidget(edit=widget, - text=ba.Lstr(resource='onText') if value else ba.Lstr( - resource='offText')) + def _check_value_change( + self, setting_name: str, widget: ba.Widget, value: int + ) -> None: + ba.textwidget( + edit=widget, + text=ba.Lstr(resource='onText') + if value + else ba.Lstr(resource='offText'), + ) self._settings[setting_name] = value def _getconfig(self) -> dict[str, Any]: @@ -466,9 +572,15 @@ class PlaylistEditGameWindow(ba.Window): def _add(self) -> None: self._completion_call(copy.deepcopy(self._getconfig())) - def _inc(self, ctrl: ba.Widget, min_val: int | float, max_val: int | float, - increment: int | float, setting_type: type, - setting_name: str) -> None: + def _inc( + self, + ctrl: ba.Widget, + min_val: int | float, + max_val: int | float, + increment: int | float, + setting_type: type, + setting_name: str, + ) -> None: if setting_type == float: val = float(cast(str, ba.textwidget(query=ctrl))) else: diff --git a/dist/ba_data/python/bastd/ui/playlist/mapselect.py b/dist/ba_data/python/bastd/ui/playlist/mapselect.py index 7e23d6a..5ced52a 100644 --- a/dist/ba_data/python/bastd/ui/playlist/mapselect.py +++ b/dist/ba_data/python/bastd/ui/playlist/mapselect.py @@ -17,14 +17,17 @@ if TYPE_CHECKING: class PlaylistMapSelectWindow(ba.Window): """Window to select a map.""" - def __init__(self, - gametype: type[ba.GameActivity], - sessiontype: type[ba.Session], - config: dict[str, Any], - edit_info: dict[str, Any], - completion_call: Callable[[dict[str, Any] | None], Any], - transition: str = 'in_right'): + def __init__( + self, + gametype: type[ba.GameActivity], + sessiontype: type[ba.Session], + config: dict[str, Any], + edit_info: dict[str, Any], + completion_call: Callable[[dict[str, Any] | None], Any], + transition: str = 'in_right', + ): from ba.internal import get_filtered_map_name + self._gametype = gametype self._sessiontype = sessiontype self._config = config @@ -33,23 +36,39 @@ class PlaylistMapSelectWindow(ba.Window): self._maps: list[tuple[str, ba.Texture]] = [] try: self._previous_map = get_filtered_map_name( - config['settings']['map']) + config['settings']['map'] + ) except Exception: self._previous_map = '' uiscale = ba.app.ui.uiscale width = 715 if uiscale is ba.UIScale.SMALL else 615 x_inset = 50 if uiscale is ba.UIScale.SMALL else 0 - height = (400 if uiscale is ba.UIScale.SMALL else - 480 if uiscale is ba.UIScale.MEDIUM else 600) + height = ( + 400 + if uiscale is ba.UIScale.SMALL + else 480 + if uiscale is ba.UIScale.MEDIUM + else 600 + ) top_extra = 20 if uiscale is ba.UIScale.SMALL else 0 - super().__init__(root_widget=ba.containerwidget( - size=(width, height + top_extra), - transition=transition, - scale=(2.17 if uiscale is ba.UIScale.SMALL else - 1.3 if uiscale is ba.UIScale.MEDIUM else 1.0), - stack_offset=(0, -27) if uiscale is ba.UIScale.SMALL else (0, 0))) + super().__init__( + root_widget=ba.containerwidget( + size=(width, height + top_extra), + transition=transition, + scale=( + 2.17 + if uiscale is ba.UIScale.SMALL + else 1.3 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + stack_offset=(0, -27) + if uiscale is ba.UIScale.SMALL + else (0, 0), + ) + ) self._cancel_button = btn = ba.buttonwidget( parent=self._root_widget, @@ -59,21 +78,24 @@ class PlaylistMapSelectWindow(ba.Window): text_scale=1.0, autoselect=True, label=ba.Lstr(resource='cancelText'), - on_activate_call=self._cancel) + on_activate_call=self._cancel, + ) ba.containerwidget(edit=self._root_widget, cancel_button=btn) - ba.textwidget(parent=self._root_widget, - position=(width * 0.5, height - 46), - size=(0, 0), - maxwidth=260, - scale=1.1, - text=ba.Lstr(resource='mapSelectTitleText', - subs=[('${GAME}', - self._gametype.get_display_string()) - ]), - color=ba.app.ui.title_color, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(width * 0.5, height - 46), + size=(0, 0), + maxwidth=260, + scale=1.1, + text=ba.Lstr( + resource='mapSelectTitleText', + subs=[('${GAME}', self._gametype.get_display_string())], + ), + color=ba.app.ui.title_color, + h_align='center', + v_align='center', + ) v = height - 70 self._scroll_width = width - (80 + 2 * x_inset) self._scroll_height = height - 140 @@ -81,9 +103,11 @@ class PlaylistMapSelectWindow(ba.Window): self._scrollwidget = ba.scrollwidget( parent=self._root_widget, position=(40 + x_inset, v - self._scroll_height), - size=(self._scroll_width, self._scroll_height)) - ba.containerwidget(edit=self._root_widget, - selected_child=self._scrollwidget) + size=(self._scroll_width, self._scroll_height), + ) + ba.containerwidget( + edit=self._root_widget, selected_child=self._scrollwidget + ) ba.containerwidget(edit=self._scrollwidget, claims_left_right=True) self._subcontainer: ba.Widget | None = None @@ -93,8 +117,11 @@ class PlaylistMapSelectWindow(ba.Window): # pylint: disable=too-many-statements # pylint: disable=too-many-branches # pylint: disable=too-many-locals - from ba.internal import (get_unowned_maps, get_map_class, - get_map_display_string) + from ba.internal import ( + get_unowned_maps, + get_map_class, + get_map_display_string, + ) # Kill old. if self._subcontainer is not None: @@ -114,7 +141,7 @@ class PlaylistMapSelectWindow(ba.Window): # Disallow ones we don't own. if mapname in unowned_maps: continue - map_tex_name = (get_map_class(mapname).get_preview_texture_name()) + map_tex_name = get_map_class(mapname).get_preview_texture_name() if map_tex_name is not None: try: map_tex = ba.gettexture(map_tex_name) @@ -132,60 +159,75 @@ class PlaylistMapSelectWindow(ba.Window): button_buffer_h = 16 button_buffer_v = 19 self._sub_width = self._scroll_width * 0.95 - self._sub_height = 5 + rows * (button_height + - 2 * button_buffer_v) + 100 - self._subcontainer = ba.containerwidget(parent=self._scrollwidget, - size=(self._sub_width, - self._sub_height), - background=False) + self._sub_height = ( + 5 + rows * (button_height + 2 * button_buffer_v) + 100 + ) + self._subcontainer = ba.containerwidget( + parent=self._scrollwidget, + size=(self._sub_width, self._sub_height), + background=False, + ) index = 0 mask_texture = ba.gettexture('mapPreviewMask') h_offs = 130 if len(self._maps) == 1 else 0 for y in range(rows): for x in range(columns): - pos = (x * (button_width + 2 * button_buffer_h) + - button_buffer_h + h_offs, self._sub_height - (y + 1) * - (button_height + 2 * button_buffer_v) + 12) - btn = ba.buttonwidget(parent=self._subcontainer, - button_type='square', - size=(button_width, button_height), - autoselect=True, - texture=self._maps[index][1], - mask_texture=mask_texture, - model_opaque=model_opaque, - model_transparent=model_transparent, - label='', - color=(1, 1, 1), - on_activate_call=ba.Call( - self._select_with_delay, - self._maps[index][0]), - position=pos) + pos = ( + x * (button_width + 2 * button_buffer_h) + + button_buffer_h + + h_offs, + self._sub_height + - (y + 1) * (button_height + 2 * button_buffer_v) + + 12, + ) + btn = ba.buttonwidget( + parent=self._subcontainer, + button_type='square', + size=(button_width, button_height), + autoselect=True, + texture=self._maps[index][1], + mask_texture=mask_texture, + model_opaque=model_opaque, + model_transparent=model_transparent, + label='', + color=(1, 1, 1), + on_activate_call=ba.Call( + self._select_with_delay, self._maps[index][0] + ), + position=pos, + ) if x == 0: ba.widget(edit=btn, left_widget=self._cancel_button) if y == 0: ba.widget(edit=btn, up_widget=self._cancel_button) if x == columns - 1 and ba.app.ui.use_toolbars: - ba.widget(edit=btn, - right_widget=ba.internal.get_special_widget( - 'party_button')) + ba.widget( + edit=btn, + right_widget=ba.internal.get_special_widget( + 'party_button' + ), + ) ba.widget(edit=btn, show_buffer_top=60, show_buffer_bottom=60) if self._maps[index][0] == self._previous_map: - ba.containerwidget(edit=self._subcontainer, - selected_child=btn, - visible_child=btn) + ba.containerwidget( + edit=self._subcontainer, + selected_child=btn, + visible_child=btn, + ) name = get_map_display_string(self._maps[index][0]) - ba.textwidget(parent=self._subcontainer, - text=name, - position=(pos[0] + button_width * 0.5, - pos[1] - 12), - size=(0, 0), - scale=0.5, - maxwidth=button_width, - draw_controller=btn, - h_align='center', - v_align='center', - color=(0.8, 0.8, 0.8, 0.8)) + ba.textwidget( + parent=self._subcontainer, + text=name, + position=(pos[0] + button_width * 0.5, pos[1] - 12), + size=(0, 0), + scale=0.5, + maxwidth=button_width, + draw_controller=btn, + h_align='center', + v_align='center', + color=(0.8, 0.8, 0.8, 0.8), + ) index += 1 if index >= count: @@ -200,29 +242,34 @@ class PlaylistMapSelectWindow(ba.Window): on_activate_call=self._on_store_press, color=(0.6, 0.53, 0.63), textcolor=(0.75, 0.7, 0.8), - autoselect=True) + autoselect=True, + ) ba.widget(edit=btn, show_buffer_top=30, show_buffer_bottom=30) if select_get_more_maps_button: - ba.containerwidget(edit=self._subcontainer, - selected_child=btn, - visible_child=btn) + ba.containerwidget( + edit=self._subcontainer, selected_child=btn, visible_child=btn + ) def _on_store_press(self) -> None: from bastd.ui import account from bastd.ui.store.browser import StoreBrowserWindow + if ba.internal.get_v1_account_state() != 'signed_in': account.show_sign_in_prompt() return - StoreBrowserWindow(modal=True, - show_tab=StoreBrowserWindow.TabID.MAPS, - on_close_call=self._on_store_close, - origin_widget=self._get_more_maps_button) + StoreBrowserWindow( + modal=True, + show_tab=StoreBrowserWindow.TabID.MAPS, + on_close_call=self._on_store_close, + origin_widget=self._get_more_maps_button, + ) def _on_store_close(self) -> None: self._refresh(select_get_more_maps_button=True) def _select(self, map_name: str) -> None: from bastd.ui.playlist.editgame import PlaylistEditGameWindow + self._config['settings']['map'] = map_name ba.containerwidget(edit=self._root_widget, transition='out_right') ba.app.ui.set_main_menu_window( @@ -233,17 +280,20 @@ class PlaylistMapSelectWindow(ba.Window): self._completion_call, default_selection='map', transition='in_left', - edit_info=self._edit_info).get_root_widget()) + edit_info=self._edit_info, + ).get_root_widget() + ) def _select_with_delay(self, map_name: str) -> None: ba.internal.lock_all_input() ba.timer(0.1, ba.internal.unlock_all_input, timetype=ba.TimeType.REAL) - ba.timer(0.1, - ba.WeakCall(self._select, map_name), - timetype=ba.TimeType.REAL) + ba.timer( + 0.1, ba.WeakCall(self._select, map_name), timetype=ba.TimeType.REAL + ) def _cancel(self) -> None: from bastd.ui.playlist.editgame import PlaylistEditGameWindow + ba.containerwidget(edit=self._root_widget, transition='out_right') ba.app.ui.set_main_menu_window( PlaylistEditGameWindow( @@ -253,4 +303,6 @@ class PlaylistMapSelectWindow(ba.Window): self._completion_call, default_selection='map', transition='in_left', - edit_info=self._edit_info).get_root_widget()) + edit_info=self._edit_info, + ).get_root_widget() + ) diff --git a/dist/ba_data/python/bastd/ui/playlist/share.py b/dist/ba_data/python/bastd/ui/playlist/share.py index 9726545..2118c53 100644 --- a/dist/ba_data/python/bastd/ui/playlist/share.py +++ b/dist/ba_data/python/bastd/ui/playlist/share.py @@ -18,12 +18,14 @@ if TYPE_CHECKING: class SharePlaylistImportWindow(promocode.PromoCodeWindow): """Window for importing a shared playlist.""" - def __init__(self, - origin_widget: ba.Widget | None = None, - on_success_callback: Callable[[], Any] | None = None): - promocode.PromoCodeWindow.__init__(self, - modal=True, - origin_widget=origin_widget) + def __init__( + self, + origin_widget: ba.Widget | None = None, + on_success_callback: Callable[[], Any] | None = None, + ): + promocode.PromoCodeWindow.__init__( + self, modal=True, origin_widget=origin_widget + ) self._on_success_callback = on_success_callback def _on_import_response(self, response: dict[str, Any] | None) -> None: @@ -39,24 +41,32 @@ class SharePlaylistImportWindow(promocode.PromoCodeWindow): else: playlist_type_name = ba.Lstr(value=response['playlistType']) - ba.screenmessage(ba.Lstr(resource='importPlaylistSuccessText', - subs=[('${TYPE}', playlist_type_name), - ('${NAME}', response['playlistName'])]), - color=(0, 1, 0)) + ba.screenmessage( + ba.Lstr( + resource='importPlaylistSuccessText', + subs=[ + ('${TYPE}', playlist_type_name), + ('${NAME}', response['playlistName']), + ], + ), + color=(0, 1, 0), + ) ba.playsound(ba.getsound('gunCocking')) if self._on_success_callback is not None: self._on_success_callback() - ba.containerwidget(edit=self._root_widget, - transition=self._transition_out) + ba.containerwidget( + edit=self._root_widget, transition=self._transition_out + ) def _do_enter(self) -> None: ba.internal.add_transaction( { 'type': 'IMPORT_PLAYLIST', 'expire_time': time.time() + 5, - 'code': ba.textwidget(query=self._text_field) + 'code': ba.textwidget(query=self._text_field), }, - callback=ba.WeakCall(self._on_import_response)) + callback=ba.WeakCall(self._on_import_response), + ) ba.internal.run_transactions() ba.screenmessage(ba.Lstr(resource='importingText')) @@ -64,47 +74,60 @@ class SharePlaylistImportWindow(promocode.PromoCodeWindow): class SharePlaylistResultsWindow(ba.Window): """Window for sharing playlists.""" - def __init__(self, - name: str, - data: str, - origin: tuple[float, float] = (0.0, 0.0)): + def __init__( + self, name: str, data: str, origin: tuple[float, float] = (0.0, 0.0) + ): del origin # unused arg self._width = 450 self._height = 300 uiscale = ba.app.ui.uiscale - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height), - color=(0.45, 0.63, 0.15), - transition='in_scale', - scale=(1.8 if uiscale is ba.UIScale.SMALL else - 1.35 if uiscale is ba.UIScale.MEDIUM else 1.0))) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height), + color=(0.45, 0.63, 0.15), + transition='in_scale', + scale=( + 1.8 + if uiscale is ba.UIScale.SMALL + else 1.35 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + ) + ) ba.playsound(ba.getsound('cashRegister')) ba.playsound(ba.getsound('swish')) - self._cancel_button = ba.buttonwidget(parent=self._root_widget, - scale=0.7, - position=(40, self._height - 40), - size=(50, 50), - label='', - on_activate_call=self.close, - autoselect=True, - color=(0.45, 0.63, 0.15), - icon=ba.gettexture('crossOut'), - iconscale=1.2) - ba.containerwidget(edit=self._root_widget, - cancel_button=self._cancel_button) + self._cancel_button = ba.buttonwidget( + parent=self._root_widget, + scale=0.7, + position=(40, self._height - 40), + size=(50, 50), + label='', + on_activate_call=self.close, + autoselect=True, + color=(0.45, 0.63, 0.15), + icon=ba.gettexture('crossOut'), + iconscale=1.2, + ) + ba.containerwidget( + edit=self._root_widget, cancel_button=self._cancel_button + ) - ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, self._height * 0.745), - size=(0, 0), - color=ba.app.ui.infotextcolor, - scale=1.0, - flatness=1.0, - h_align='center', - v_align='center', - text=ba.Lstr(resource='exportSuccessText', - subs=[('${NAME}', name)]), - maxwidth=self._width * 0.85) + ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, self._height * 0.745), + size=(0, 0), + color=ba.app.ui.infotextcolor, + scale=1.0, + flatness=1.0, + h_align='center', + v_align='center', + text=ba.Lstr( + resource='exportSuccessText', subs=[('${NAME}', name)] + ), + maxwidth=self._width * 0.85, + ) ba.textwidget( parent=self._root_widget, @@ -116,17 +139,20 @@ class SharePlaylistResultsWindow(ba.Window): h_align='center', v_align='center', text=ba.Lstr(resource='importPlaylistCodeInstructionsText'), - maxwidth=self._width * 0.85) + maxwidth=self._width * 0.85, + ) - ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, self._height * 0.4), - size=(0, 0), - color=(1.0, 3.0, 1.0), - scale=2.3, - h_align='center', - v_align='center', - text=data, - maxwidth=self._width * 0.85) + ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, self._height * 0.4), + size=(0, 0), + color=(1.0, 3.0, 1.0), + scale=2.3, + h_align='center', + v_align='center', + text=data, + maxwidth=self._width * 0.85, + ) def close(self) -> None: """Close the window.""" diff --git a/dist/ba_data/python/bastd/ui/playoptions.py b/dist/ba_data/python/bastd/ui/playoptions.py index 8dfb4aa..507550b 100644 --- a/dist/ba_data/python/bastd/ui/playoptions.py +++ b/dist/ba_data/python/bastd/ui/playoptions.py @@ -17,11 +17,13 @@ if TYPE_CHECKING: class PlayOptionsWindow(popup.PopupWindow): """A popup window for configuring play options.""" - def __init__(self, - sessiontype: type[ba.Session], - playlist: str, - scale_origin: tuple[float, float], - delegate: Any = None): + def __init__( + self, + sessiontype: type[ba.Session], + playlist: str, + scale_origin: tuple[float, float], + delegate: Any = None, + ): # FIXME: Tidy this up. # pylint: disable=too-many-branches # pylint: disable=too-many-statements @@ -38,8 +40,9 @@ class PlayOptionsWindow(popup.PopupWindow): # vs starting a game directly (should make this more elegant). self._selecting_mode = ba.app.ui.selecting_private_party_playlist - self._do_randomize_val = (ba.app.config.get( - self._pvars.config_name + ' Playlist Randomize', 0)) + self._do_randomize_val = ba.app.config.get( + self._pvars.config_name + ' Playlist Randomize', 0 + ) self._sessiontype = sessiontype self._playlist = playlist @@ -73,21 +76,30 @@ class PlayOptionsWindow(popup.PopupWindow): plst = self._pvars.get_default_list_call() else: try: - plst = ba.app.config[self._pvars.config_name + - ' Playlists'][name] + plst = ba.app.config[ + self._pvars.config_name + ' Playlists' + ][name] except Exception: - print('ERROR INFO: self._config_name is:', - self._pvars.config_name) + print( + 'ERROR INFO: self._config_name is:', + self._pvars.config_name, + ) print( 'ERROR INFO: playlist names are:', - list(ba.app.config[self._pvars.config_name + - ' Playlists'].keys())) + list( + ba.app.config[ + self._pvars.config_name + ' Playlists' + ].keys() + ), + ) raise - plst = filter_playlist(plst, - self._sessiontype, - remove_unowned=False, - mark_unowned=True, - name=name) + plst = filter_playlist( + plst, + self._sessiontype, + remove_unowned=False, + mark_unowned=True, + name=name, + ) game_count = len(plst) for entry in plst: mapname = entry['settings']['map'] @@ -127,25 +139,33 @@ class PlayOptionsWindow(popup.PopupWindow): # Creates our _root_widget. uiscale = ba.app.ui.uiscale - scale = (1.69 if uiscale is ba.UIScale.SMALL else - 1.1 if uiscale is ba.UIScale.MEDIUM else 0.85) - super().__init__(position=scale_origin, - size=(self._width, self._height), - scale=scale) + scale = ( + 1.69 + if uiscale is ba.UIScale.SMALL + else 1.1 + if uiscale is ba.UIScale.MEDIUM + else 0.85 + ) + super().__init__( + position=scale_origin, size=(self._width, self._height), scale=scale + ) - playlist_name: str | ba.Lstr = (self._pvars.default_list_name - if playlist == '__default__' else - playlist) - self._title_text = ba.textwidget(parent=self.root_widget, - position=(self._width * 0.5, - self._height - 89 + 51), - size=(0, 0), - text=playlist_name, - scale=1.4, - color=(1, 1, 1), - maxwidth=self._width * 0.7, - h_align='center', - v_align='center') + playlist_name: str | ba.Lstr = ( + self._pvars.default_list_name + if playlist == '__default__' + else playlist + ) + self._title_text = ba.textwidget( + parent=self.root_widget, + position=(self._width * 0.5, self._height - 89 + 51), + size=(0, 0), + text=playlist_name, + scale=1.4, + color=(1, 1, 1), + maxwidth=self._width * 0.7, + h_align='center', + v_align='center', + ) self._cancel_button = ba.buttonwidget( parent=self.root_widget, @@ -157,7 +177,8 @@ class PlayOptionsWindow(popup.PopupWindow): on_activate_call=self._on_cancel_press, autoselect=True, icon=ba.gettexture('crossOut'), - iconscale=1.2) + iconscale=1.2, + ) h_offs_img = self._width * 0.5 - c_width_total * 0.5 v_offs_img = self._height - 118 - scl * 125.0 + 50 @@ -172,27 +193,34 @@ class PlayOptionsWindow(popup.PopupWindow): h = h_offs_img + scl * 250 * col v = v_offs_img - self._row_height * row entry = map_texture_entries[tex_index] - owned = not (('is_unowned_map' in entry - and entry['is_unowned_map']) or - ('is_unowned_game' in entry - and entry['is_unowned_game'])) + owned = not ( + ('is_unowned_map' in entry and entry['is_unowned_map']) + or ( + 'is_unowned_game' in entry + and entry['is_unowned_game'] + ) + ) if owned: self._have_at_least_one_owned = True try: - desc = getclass(entry['type'], - subclassof=ba.GameActivity - ).get_settings_display_string(entry) + desc = getclass( + entry['type'], subclassof=ba.GameActivity + ).get_settings_display_string(entry) if not owned: desc = ba.Lstr( value='${DESC}\n${UNLOCK}', subs=[ ('${DESC}', desc), - ('${UNLOCK}', - ba.Lstr( - resource='unlockThisInTheStoreText')) - ]) + ( + '${UNLOCK}', + ba.Lstr( + resource='unlockThisInTheStoreText' + ), + ), + ], + ) desc_color = (0, 1, 0) if owned else (1, 0, 0) except Exception: desc = ba.Lstr(value='(invalid)') @@ -204,14 +232,16 @@ class PlayOptionsWindow(popup.PopupWindow): position=(h, v), texture=ba.gettexture(tex_name if owned else 'empty'), model_opaque=model_opaque if owned else None, - on_activate_call=ba.Call(ba.screenmessage, desc, - desc_color), + on_activate_call=ba.Call( + ba.screenmessage, desc, desc_color + ), label='', color=(1, 1, 1), autoselect=True, extra_touch_border_scale=0.0, model_transparent=model_transparent if owned else None, - mask_texture=mask_tex if owned else None) + mask_texture=mask_tex if owned else None, + ) if row == 0 and col == 0: ba.widget(edit=self._cancel_button, down_widget=btn) if row == rows - 1: @@ -221,23 +251,26 @@ class PlayOptionsWindow(popup.PopupWindow): # Ewww; buttons don't currently have alpha so in this # case we draw an image over our button with an empty # texture on it. - ba.imagewidget(parent=self.root_widget, - size=(scl * 260.0, scl * 130.0), - position=(h - 10.0 * scl, - v - 4.0 * scl), - draw_controller=btn, - color=(1, 1, 1), - texture=ba.gettexture(tex_name), - model_opaque=model_opaque, - opacity=0.25, - model_transparent=model_transparent, - mask_texture=mask_tex) + ba.imagewidget( + parent=self.root_widget, + size=(scl * 260.0, scl * 130.0), + position=(h - 10.0 * scl, v - 4.0 * scl), + draw_controller=btn, + color=(1, 1, 1), + texture=ba.gettexture(tex_name), + model_opaque=model_opaque, + opacity=0.25, + model_transparent=model_transparent, + mask_texture=mask_tex, + ) - ba.imagewidget(parent=self.root_widget, - size=(scl * 100, scl * 100), - draw_controller=btn, - position=(h + scl * 70, v + scl * 10), - texture=ba.gettexture('lock')) + ba.imagewidget( + parent=self.root_widget, + size=(scl * 100, scl * 100), + draw_controller=btn, + position=(h + scl * 70, v + scl * 10), + texture=ba.gettexture('lock'), + ) # Team names/colors. self._custom_colors_names_button: ba.Widget | None @@ -250,14 +283,16 @@ class PlayOptionsWindow(popup.PopupWindow): on_activate_call=ba.WeakCall(self._custom_colors_names_press), autoselect=True, textcolor=(0.8, 0.8, 0.8), - label=ba.Lstr(resource='teamNamesColorText')) + label=ba.Lstr(resource='teamNamesColorText'), + ) if not ba.app.accounts_v1.have_pro(): ba.imagewidget( parent=self.root_widget, size=(30, 30), position=(95, 202 + y_offs), texture=ba.gettexture('lock'), - draw_controller=self._custom_colors_names_button) + draw_controller=self._custom_colors_names_button, + ) else: self._custom_colors_names_button = None @@ -265,8 +300,9 @@ class PlayOptionsWindow(popup.PopupWindow): def _cb_callback(val: bool) -> None: self._do_randomize_val = val cfg = ba.app.config - cfg[self._pvars.config_name + - ' Playlist Randomize'] = self._do_randomize_val + cfg[ + self._pvars.config_name + ' Playlist Randomize' + ] = self._do_randomize_val cfg.commit() if show_shuffle_check_box: @@ -280,7 +316,8 @@ class PlayOptionsWindow(popup.PopupWindow): maxwidth=300, textcolor=(0.8, 0.8, 0.8), value=self._do_randomize_val, - on_value_change_call=_cb_callback) + on_value_change_call=_cb_callback, + ) # Show tutorial. show_tutorial = bool(ba.app.config.get('Show Tutorial', True)) @@ -300,24 +337,34 @@ class PlayOptionsWindow(popup.PopupWindow): maxwidth=300, textcolor=(0.8, 0.8, 0.8), value=show_tutorial, - on_value_change_call=_cb_callback_2) + on_value_change_call=_cb_callback_2, + ) # Grumble: current autoselect doesn't do a very good job # with checkboxes. if self._custom_colors_names_button is not None: for btn in bottom_row_buttons: - ba.widget(edit=btn, - down_widget=self._custom_colors_names_button) + ba.widget( + edit=btn, down_widget=self._custom_colors_names_button + ) if show_shuffle_check_box: - ba.widget(edit=self._custom_colors_names_button, - down_widget=self._shuffle_check_box) - ba.widget(edit=self._shuffle_check_box, - up_widget=self._custom_colors_names_button) + ba.widget( + edit=self._custom_colors_names_button, + down_widget=self._shuffle_check_box, + ) + ba.widget( + edit=self._shuffle_check_box, + up_widget=self._custom_colors_names_button, + ) else: - ba.widget(edit=self._custom_colors_names_button, - down_widget=self._show_tutorial_check_box) - ba.widget(edit=self._show_tutorial_check_box, - up_widget=self._custom_colors_names_button) + ba.widget( + edit=self._custom_colors_names_button, + down_widget=self._show_tutorial_check_box, + ) + ba.widget( + edit=self._show_tutorial_check_box, + up_widget=self._custom_colors_names_button, + ) self._ok_button = ba.buttonwidget( parent=self.root_widget, @@ -328,27 +375,33 @@ class PlayOptionsWindow(popup.PopupWindow): on_activate_call=self._on_ok_press, autoselect=True, label=ba.Lstr( - resource='okText' if self._selecting_mode else 'playText')) + resource='okText' if self._selecting_mode else 'playText' + ), + ) - ba.widget(edit=self._ok_button, - up_widget=self._show_tutorial_check_box) + ba.widget(edit=self._ok_button, up_widget=self._show_tutorial_check_box) - ba.containerwidget(edit=self.root_widget, - start_button=self._ok_button, - cancel_button=self._cancel_button, - selected_child=self._ok_button) + ba.containerwidget( + edit=self.root_widget, + start_button=self._ok_button, + cancel_button=self._cancel_button, + selected_child=self._ok_button, + ) # Update now and once per second. - self._update_timer = ba.Timer(1.0, - ba.WeakCall(self._update), - timetype=ba.TimeType.REAL, - repeat=True) + self._update_timer = ba.Timer( + 1.0, + ba.WeakCall(self._update), + timetype=ba.TimeType.REAL, + repeat=True, + ) self._update() def _custom_colors_names_press(self) -> None: from bastd.ui.account import show_sign_in_prompt from bastd.ui.teamnamescolors import TeamNamesColorsWindow from bastd.ui.purchase import PurchaseWindow + if not ba.app.accounts_v1.have_pro(): if ba.internal.get_v1_account_state() != 'signed_in': show_sign_in_prompt() @@ -357,14 +410,18 @@ class PlayOptionsWindow(popup.PopupWindow): self._transition_out() return assert self._custom_colors_names_button - TeamNamesColorsWindow(scale_origin=self._custom_colors_names_button. - get_screen_space_center()) + TeamNamesColorsWindow( + scale_origin=( + self._custom_colors_names_button.get_screen_space_center() + ) + ) def _does_target_playlist_exist(self) -> bool: if self._playlist == '__default__': return True return self._playlist in ba.app.config.get( - self._pvars.config_name + ' Playlists', {}) + self._pvars.config_name + ' Playlists', {} + ) def _update(self) -> None: # All we do here is make sure our targeted playlist still exists, @@ -393,8 +450,10 @@ class PlayOptionsWindow(popup.PopupWindow): # Disallow if we have no unlocked games. if not self._have_at_least_one_owned: ba.playsound(ba.getsound('error')) - ba.screenmessage(ba.Lstr(resource='playlistNoValidGamesErrorText'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr(resource='playlistNoValidGamesErrorText'), + color=(1, 0, 0), + ) return cfg = ba.app.config @@ -404,6 +463,7 @@ class PlayOptionsWindow(popup.PopupWindow): # or start the game in regular mode. if self._selecting_mode: from bastd.ui.gather import GatherWindow + if self._sessiontype is ba.FreeForAllSession: typename = 'ffa' elif self._sessiontype is ba.DualTeamSession: @@ -413,7 +473,8 @@ class PlayOptionsWindow(popup.PopupWindow): cfg['Private Party Host Session Type'] = typename ba.playsound(ba.getsound('gunCocking')) ba.app.ui.set_main_menu_window( - GatherWindow(transition='in_right').get_root_widget()) + GatherWindow(transition='in_right').get_root_widget() + ) self._transition_out(transition='out_left') if self._delegate is not None: self._delegate.on_play_options_window_run_game() @@ -432,6 +493,7 @@ class PlayOptionsWindow(popup.PopupWindow): ba.internal.new_host_session(self._sessiontype) except Exception: from bastd import mainmenu + ba.print_exception('exception running session', self._sessiontype) # Drop back into a main menu session. diff --git a/dist/ba_data/python/bastd/ui/popup.py b/dist/ba_data/python/bastd/ui/popup.py index 8f918fe..79d3de5 100644 --- a/dist/ba_data/python/bastd/ui/popup.py +++ b/dist/ba_data/python/bastd/ui/popup.py @@ -19,15 +19,17 @@ class PopupWindow: Category: UI Classes""" - def __init__(self, - position: tuple[float, float], - size: tuple[float, float], - scale: float = 1.0, - offset: tuple[float, float] = (0, 0), - bg_color: tuple[float, float, float] = (0.35, 0.55, 0.15), - focus_position: tuple[float, float] = (0, 0), - focus_size: tuple[float, float] | None = None, - toolbar_visibility: str = 'menu_minimal_no_back'): + def __init__( + self, + position: tuple[float, float], + size: tuple[float, float], + scale: float = 1.0, + offset: tuple[float, float] = (0, 0), + bg_color: tuple[float, float, float] = (0.35, 0.55, 0.15), + focus_position: tuple[float, float] = (0, 0), + focus_size: tuple[float, float] | None = None, + toolbar_visibility: str = 'menu_minimal_no_back', + ): # pylint: disable=too-many-locals if focus_size is None: focus_size = size @@ -45,17 +47,17 @@ class PopupWindow: # need be and clamping it to the UI bounds. bounds = ba.app.ui_bounds edge_buffer = 15 - bounds_width = (bounds[1] - bounds[0] - edge_buffer * 2) - bounds_height = (bounds[3] - bounds[2] - edge_buffer * 2) + bounds_width = bounds[1] - bounds[0] - edge_buffer * 2 + bounds_height = bounds[3] - bounds[2] - edge_buffer * 2 fin_width = width * scale fin_height = height * scale if fin_width > bounds_width: - scale /= (fin_width / bounds_width) + scale /= fin_width / bounds_width fin_width = width * scale fin_height = height * scale if fin_height > bounds_height: - scale /= (fin_height / bounds_height) + scale /= fin_height / bounds_height fin_width = width * scale fin_height = height * scale @@ -71,10 +73,12 @@ class PopupWindow: # focus area. ..now calc the difference between the center of our # focus area and the center of our window to come up with the # offset we'll need to plug in to the window - x_offs = ((focus_position[0] + focus_size[0] * 0.5) - - (size[0] * 0.5)) * scale - y_offs = ((focus_position[1] + focus_size[1] * 0.5) - - (size[1] * 0.5)) * scale + x_offs = ( + (focus_position[0] + focus_size[0] * 0.5) - (size[0] * 0.5) + ) * scale + y_offs = ( + (focus_position[1] + focus_size[1] * 0.5) - (size[1] * 0.5) + ) * scale self.root_widget = ba.containerwidget( transition='in_scale', @@ -87,7 +91,8 @@ class PopupWindow: on_outside_click_call=self.on_popup_cancel, claim_outside_clicks=True, color=bg_color, - on_cancel_call=self.on_popup_cancel) + on_cancel_call=self.on_popup_cancel, + ) # complain if we outlive our root widget ba.uicleanupcheck(self, self.root_widget) @@ -102,16 +107,18 @@ class PopupWindow: class PopupMenuWindow(PopupWindow): """A menu built using popup-window functionality.""" - def __init__(self, - position: tuple[float, float], - choices: Sequence[str], - current_choice: str, - delegate: Any = None, - width: float = 230.0, - maxwidth: float | None = None, - scale: float = 1.0, - choices_disabled: Sequence[str] | None = None, - choices_display: Sequence[ba.Lstr] | None = None): + def __init__( + self, + position: tuple[float, float], + choices: Sequence[str], + current_choice: str, + delegate: Any = None, + width: float = 230.0, + maxwidth: float | None = None, + scale: float = 1.0, + choices_disabled: Sequence[str] | None = None, + choices_display: Sequence[ba.Lstr] | None = None, + ): # FIXME: Clean up a bit. # pylint: disable=too-many-branches # pylint: disable=too-many-locals @@ -160,65 +167,81 @@ class PopupMenuWindow(PopupWindow): min( maxwidth, ba.internal.get_string_width( - choice_display_name, suppress_warning=True)) + 75) + choice_display_name, suppress_warning=True + ), + ) + + 75, + ) else: self._width = max( self._width, min( maxwidth, ba.internal.get_string_width( - choice_display_name, suppress_warning=True)) + 60) + choice_display_name, suppress_warning=True + ), + ) + + 60, + ) # init parent class - this will rescale and reposition things as # needed and create our root widget - PopupWindow.__init__(self, - position, - size=(self._width, self._height), - scale=self._scale) + PopupWindow.__init__( + self, position, size=(self._width, self._height), scale=self._scale + ) if self._use_scroll: - self._scrollwidget = ba.scrollwidget(parent=self.root_widget, - position=(20, 20), - highlight=False, - color=(0.35, 0.55, 0.15), - size=(self._width - 40, - self._height - 40)) - self._columnwidget = ba.columnwidget(parent=self._scrollwidget, - border=2, - margin=0) + self._scrollwidget = ba.scrollwidget( + parent=self.root_widget, + position=(20, 20), + highlight=False, + color=(0.35, 0.55, 0.15), + size=(self._width - 40, self._height - 40), + ) + self._columnwidget = ba.columnwidget( + parent=self._scrollwidget, border=2, margin=0 + ) else: - self._offset_widget = ba.containerwidget(parent=self.root_widget, - position=(30, 15), - size=(self._width - 40, - self._height), - background=False) - self._columnwidget = ba.columnwidget(parent=self._offset_widget, - border=2, - margin=0) + self._offset_widget = ba.containerwidget( + parent=self.root_widget, + position=(30, 15), + size=(self._width - 40, self._height), + background=False, + ) + self._columnwidget = ba.columnwidget( + parent=self._offset_widget, border=2, margin=0 + ) for index, choice in enumerate(choices): if len(choices_display_fin) == len(choices): choice_display_name = choices_display_fin[index] else: choice_display_name = choice - inactive = (choice in self._choices_disabled) - wdg = ba.textwidget(parent=self._columnwidget, - size=(self._width - 40, 28), - on_select_call=ba.Call(self._select, index), - click_activate=True, - color=(0.5, 0.5, 0.5, 0.5) if inactive else - ((0.5, 1, 0.5, - 1) if choice == self._current_choice else - (0.8, 0.8, 0.8, 1.0)), - padding=0, - maxwidth=maxwidth, - text=choice_display_name, - on_activate_call=self._activate, - v_align='center', - selectable=(not inactive)) + inactive = choice in self._choices_disabled + wdg = ba.textwidget( + parent=self._columnwidget, + size=(self._width - 40, 28), + on_select_call=ba.Call(self._select, index), + click_activate=True, + color=(0.5, 0.5, 0.5, 0.5) + if inactive + else ( + (0.5, 1, 0.5, 1) + if choice == self._current_choice + else (0.8, 0.8, 0.8, 1.0) + ), + padding=0, + maxwidth=maxwidth, + text=choice_display_name, + on_activate_call=self._activate, + v_align='center', + selectable=(not inactive), + ) if choice == self._current_choice: - ba.containerwidget(edit=self._columnwidget, - selected_child=wdg, - visible_child=wdg) + ba.containerwidget( + edit=self._columnwidget, + selected_child=wdg, + visible_child=wdg, + ) # ok from now on our delegate can be called self._delegate = weakref.ref(delegate) @@ -235,8 +258,9 @@ class PopupMenuWindow(PopupWindow): if delegate is not None: # Call this in a timer so it doesn't interfere with us killing # our widgets and whatnot. - call = ba.Call(delegate.popup_menu_selected_choice, self, - self._current_choice) + call = ba.Call( + delegate.popup_menu_selected_choice, self, self._current_choice + ) ba.timer(0, call, timetype=ba.TimeType.REAL) def _getdelegate(self) -> Any: @@ -264,21 +288,23 @@ class PopupMenu: This creates a button and wrangles its pop-up menu. """ - def __init__(self, - parent: ba.Widget, - position: tuple[float, float], - choices: Sequence[str], - current_choice: str | None = None, - on_value_change_call: Callable[[str], Any] | None = None, - opening_call: Callable[[], Any] | None = None, - closing_call: Callable[[], Any] | None = None, - width: float = 230.0, - maxwidth: float | None = None, - scale: float | None = None, - choices_disabled: Sequence[str] | None = None, - choices_display: Sequence[ba.Lstr] | None = None, - button_size: tuple[float, float] = (160.0, 50.0), - autoselect: bool = True): + def __init__( + self, + parent: ba.Widget, + position: tuple[float, float], + choices: Sequence[str], + current_choice: str | None = None, + on_value_change_call: Callable[[str], Any] | None = None, + opening_call: Callable[[], Any] | None = None, + closing_call: Callable[[], Any] | None = None, + width: float = 230.0, + maxwidth: float | None = None, + scale: float | None = None, + choices_disabled: Sequence[str] | None = None, + choices_display: Sequence[ba.Lstr] | None = None, + button_size: tuple[float, float] = (160.0, 50.0), + autoselect: bool = True, + ): # pylint: disable=too-many-locals if choices_disabled is None: choices_disabled = [] @@ -286,8 +312,13 @@ class PopupMenu: choices_display = [] uiscale = ba.app.ui.uiscale if scale is None: - scale = (2.3 if uiscale is ba.UIScale.SMALL else - 1.65 if uiscale is ba.UIScale.MEDIUM else 1.23) + scale = ( + 2.3 + if uiscale is ba.UIScale.SMALL + else 1.65 + if uiscale is ba.UIScale.MEDIUM + else 1.23 + ) if current_choice not in choices: current_choice = None self._choices = list(choices) @@ -298,8 +329,9 @@ class PopupMenu: self._width = width self._maxwidth = maxwidth self._scale = scale - self._current_choice = (current_choice if current_choice is not None - else self._choices[0]) + self._current_choice = ( + current_choice if current_choice is not None else self._choices[0] + ) self._position = position self._parent = parent if not choices: @@ -315,7 +347,9 @@ class PopupMenu: scale=1.0, label='', on_activate_call=lambda: ba.timer( - 0, self._make_popup, timetype=ba.TimeType.REAL)) + 0, self._make_popup, timetype=ba.TimeType.REAL + ), + ) self._on_value_change_call = None # Don't wanna call for initial set. self._opening_call = opening_call self._autoselect = autoselect @@ -341,7 +375,8 @@ class PopupMenu: choices=self._choices, current_choice=self._current_choice, choices_disabled=self._choices_disabled, - choices_display=self._choices_display).root_widget + choices_display=self._choices_display, + ).root_widget def get_button(self) -> ba.Widget: """Return the menu's button widget.""" @@ -351,8 +386,9 @@ class PopupMenu: """Return the menu's window widget (or None if nonexistent).""" return self._window_widget - def popup_menu_selected_choice(self, popup_window: PopupWindow, - choice: str) -> None: + def popup_menu_selected_choice( + self, popup_window: PopupWindow, choice: str + ) -> None: """Called when a choice is selected.""" del popup_window # Unused here. self.set_choice(choice) diff --git a/dist/ba_data/python/bastd/ui/profile/browser.py b/dist/ba_data/python/bastd/ui/profile/browser.py index ff8c611..350e303 100644 --- a/dist/ba_data/python/bastd/ui/profile/browser.py +++ b/dist/ba_data/python/bastd/ui/profile/browser.py @@ -16,11 +16,13 @@ if TYPE_CHECKING: class ProfileBrowserWindow(ba.Window): """Window for browsing player profiles.""" - def __init__(self, - transition: str = 'in_right', - in_main_menu: bool = True, - selected_profile: str | None = None, - origin_widget: ba.Widget | None = None): + def __init__( + self, + transition: str = 'in_right', + in_main_menu: bool = True, + selected_profile: str | None = None, + origin_widget: ba.Widget | None = None, + ): # pylint: disable=too-many-statements # pylint: disable=too-many-locals self._in_main_menu = in_main_menu @@ -31,8 +33,13 @@ class ProfileBrowserWindow(ba.Window): uiscale = ba.app.ui.uiscale self._width = 700.0 if uiscale is ba.UIScale.SMALL else 600.0 x_inset = 50.0 if uiscale is ba.UIScale.SMALL else 0.0 - self._height = (360.0 if uiscale is ba.UIScale.SMALL else - 385.0 if uiscale is ba.UIScale.MEDIUM else 410.0) + self._height = ( + 360.0 + if uiscale is ba.UIScale.SMALL + else 385.0 + if uiscale is ba.UIScale.MEDIUM + else 410.0 + ) # If we're being called up standalone, handle pause/resume ourself. if not self._in_main_menu: @@ -55,13 +62,23 @@ class ProfileBrowserWindow(ba.Window): top_extra = 20 if uiscale is ba.UIScale.SMALL else 0 - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height + top_extra), - transition=transition, - scale_origin_stack_offset=scale_origin, - scale=(2.2 if uiscale is ba.UIScale.SMALL else - 1.6 if uiscale is ba.UIScale.MEDIUM else 1.0), - stack_offset=(0, -14) if uiscale is ba.UIScale.SMALL else (0, 0))) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height + top_extra), + transition=transition, + scale_origin_stack_offset=scale_origin, + scale=( + 2.2 + if uiscale is ba.UIScale.SMALL + else 1.6 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + stack_offset=(0, -14) + if uiscale is ba.UIScale.SMALL + else (0, 0), + ) + ) self._back_button = btn = ba.buttonwidget( parent=self._root_widget, @@ -71,24 +88,29 @@ class ProfileBrowserWindow(ba.Window): label=back_label, button_type='back' if self._in_main_menu else None, autoselect=True, - on_activate_call=self._back) + on_activate_call=self._back, + ) ba.containerwidget(edit=self._root_widget, cancel_button=btn) - ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, self._height - 36), - size=(0, 0), - text=ba.Lstr(resource=self._r + '.titleText'), - maxwidth=300, - color=ba.app.ui.title_color, - scale=0.9, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, self._height - 36), + size=(0, 0), + text=ba.Lstr(resource=self._r + '.titleText'), + maxwidth=300, + color=ba.app.ui.title_color, + scale=0.9, + h_align='center', + v_align='center', + ) if self._in_main_menu: - ba.buttonwidget(edit=btn, - button_type='backSmall', - size=(60, 60), - label=ba.charstr(ba.SpecialChar.BACK)) + ba.buttonwidget( + edit=btn, + button_type='backSmall', + size=(60, 60), + label=ba.charstr(ba.SpecialChar.BACK), + ) scroll_height = self._height - 140.0 self._scroll_width = self._width - (188 + x_inset * 2) @@ -96,20 +118,26 @@ class ProfileBrowserWindow(ba.Window): h = 50 + x_inset b_color = (0.6, 0.53, 0.63) - scl = (1.055 if uiscale is ba.UIScale.SMALL else - 1.18 if uiscale is ba.UIScale.MEDIUM else 1.3) + scl = ( + 1.055 + if uiscale is ba.UIScale.SMALL + else 1.18 + if uiscale is ba.UIScale.MEDIUM + else 1.3 + ) v -= 70.0 * scl - self._new_button = ba.buttonwidget(parent=self._root_widget, - position=(h, v), - size=(80, 66.0 * scl), - on_activate_call=self._new_profile, - color=b_color, - button_type='square', - autoselect=True, - textcolor=(0.75, 0.7, 0.8), - text_scale=0.7, - label=ba.Lstr(resource=self._r + - '.newButtonText')) + self._new_button = ba.buttonwidget( + parent=self._root_widget, + position=(h, v), + size=(80, 66.0 * scl), + on_activate_call=self._new_profile, + color=b_color, + button_type='square', + autoselect=True, + textcolor=(0.75, 0.7, 0.8), + text_scale=0.7, + label=ba.Lstr(resource=self._r + '.newButtonText'), + ) v -= 70.0 * scl self._edit_button = ba.buttonwidget( parent=self._root_widget, @@ -121,7 +149,8 @@ class ProfileBrowserWindow(ba.Window): autoselect=True, textcolor=(0.75, 0.7, 0.8), text_scale=0.7, - label=ba.Lstr(resource=self._r + '.editButtonText')) + label=ba.Lstr(resource=self._r + '.editButtonText'), + ) v -= 70.0 * scl self._delete_button = ba.buttonwidget( parent=self._root_widget, @@ -133,34 +162,40 @@ class ProfileBrowserWindow(ba.Window): autoselect=True, textcolor=(0.75, 0.7, 0.8), text_scale=0.7, - label=ba.Lstr(resource=self._r + '.deleteButtonText')) + label=ba.Lstr(resource=self._r + '.deleteButtonText'), + ) v = self._height - 87 - ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, self._height - 71), - size=(0, 0), - text=ba.Lstr(resource=self._r + '.explanationText'), - color=ba.app.ui.infotextcolor, - maxwidth=self._width * 0.83, - scale=0.6, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, self._height - 71), + size=(0, 0), + text=ba.Lstr(resource=self._r + '.explanationText'), + color=ba.app.ui.infotextcolor, + maxwidth=self._width * 0.83, + scale=0.6, + h_align='center', + v_align='center', + ) - self._scrollwidget = ba.scrollwidget(parent=self._root_widget, - highlight=False, - position=(140 + x_inset, - v - scroll_height), - size=(self._scroll_width, - scroll_height)) - ba.widget(edit=self._scrollwidget, - autoselect=True, - left_widget=self._new_button) - ba.containerwidget(edit=self._root_widget, - selected_child=self._scrollwidget) - self._columnwidget = ba.columnwidget(parent=self._scrollwidget, - border=2, - margin=0) + self._scrollwidget = ba.scrollwidget( + parent=self._root_widget, + highlight=False, + position=(140 + x_inset, v - scroll_height), + size=(self._scroll_width, scroll_height), + ) + ba.widget( + edit=self._scrollwidget, + autoselect=True, + left_widget=self._new_button, + ) + ba.containerwidget( + edit=self._root_widget, selected_child=self._scrollwidget + ) + self._columnwidget = ba.columnwidget( + parent=self._scrollwidget, border=2, margin=0 + ) v -= 255 self._profiles: dict[str, dict[str, Any]] | None = None self._selected_profile = selected_profile @@ -175,23 +210,34 @@ class ProfileBrowserWindow(ba.Window): # Limit to a handful profiles if they don't have pro-options. max_non_pro_profiles = ba.internal.get_v1_account_misc_read_val( - 'mnpp', 5) + 'mnpp', 5 + ) assert self._profiles is not None - if (not ba.app.accounts_v1.have_pro_options() - and len(self._profiles) >= max_non_pro_profiles): - PurchaseWindow(items=['pro'], - header_text=ba.Lstr( - resource='unlockThisProfilesText', - subs=[('${NUM}', str(max_non_pro_profiles))])) + if ( + not ba.app.accounts_v1.have_pro_options() + and len(self._profiles) >= max_non_pro_profiles + ): + PurchaseWindow( + items=['pro'], + header_text=ba.Lstr( + resource='unlockThisProfilesText', + subs=[('${NUM}', str(max_non_pro_profiles))], + ), + ) return # Clamp at 100 profiles (otherwise the server will and that's less # elegant looking). if len(self._profiles) > 100: ba.screenmessage( - ba.Lstr(translate=('serverResponses', - 'Max number of profiles reached.')), - color=(1, 0, 0)) + ba.Lstr( + translate=( + 'serverResponses', + 'Max number of profiles reached.', + ) + ), + color=(1, 0, 0), + ) ba.playsound(ba.getsound('error')) return @@ -199,55 +245,66 @@ class ProfileBrowserWindow(ba.Window): ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.ui.set_main_menu_window( EditProfileWindow( - existing_profile=None, - in_main_menu=self._in_main_menu).get_root_widget()) + existing_profile=None, in_main_menu=self._in_main_menu + ).get_root_widget() + ) def _delete_profile(self) -> None: # pylint: disable=cyclic-import from bastd.ui import confirm + if self._selected_profile is None: ba.playsound(ba.getsound('error')) - ba.screenmessage(ba.Lstr(resource='nothingIsSelectedErrorText'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr(resource='nothingIsSelectedErrorText'), color=(1, 0, 0) + ) return if self._selected_profile == '__account__': ba.playsound(ba.getsound('error')) - ba.screenmessage(ba.Lstr(resource=self._r + - '.cantDeleteAccountProfileText'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr(resource=self._r + '.cantDeleteAccountProfileText'), + color=(1, 0, 0), + ) return confirm.ConfirmWindow( - ba.Lstr(resource=self._r + '.deleteConfirmText', - subs=[('${PROFILE}', self._selected_profile)]), - self._do_delete_profile, 350) + ba.Lstr( + resource=self._r + '.deleteConfirmText', + subs=[('${PROFILE}', self._selected_profile)], + ), + self._do_delete_profile, + 350, + ) def _do_delete_profile(self) -> None: - ba.internal.add_transaction({ - 'type': 'REMOVE_PLAYER_PROFILE', - 'name': self._selected_profile - }) + ba.internal.add_transaction( + {'type': 'REMOVE_PLAYER_PROFILE', 'name': self._selected_profile} + ) ba.internal.run_transactions() ba.playsound(ba.getsound('shieldDown')) self._refresh() # Select profile list. - ba.containerwidget(edit=self._root_widget, - selected_child=self._scrollwidget) + ba.containerwidget( + edit=self._root_widget, selected_child=self._scrollwidget + ) def _edit_profile(self) -> None: # pylint: disable=cyclic-import from bastd.ui.profile.edit import EditProfileWindow + if self._selected_profile is None: ba.playsound(ba.getsound('error')) - ba.screenmessage(ba.Lstr(resource='nothingIsSelectedErrorText'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr(resource='nothingIsSelectedErrorText'), color=(1, 0, 0) + ) return self._save_state() ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.ui.set_main_menu_window( EditProfileWindow( - self._selected_profile, - in_main_menu=self._in_main_menu).get_root_widget()) + self._selected_profile, in_main_menu=self._in_main_menu + ).get_root_widget() + ) def _select(self, name: str, index: int) -> None: del index # Unused. @@ -256,12 +313,15 @@ class ProfileBrowserWindow(ba.Window): def _back(self) -> None: # pylint: disable=cyclic-import from bastd.ui.account.settings import AccountSettingsWindow + self._save_state() - ba.containerwidget(edit=self._root_widget, - transition=self._transition_out) + ba.containerwidget( + edit=self._root_widget, transition=self._transition_out + ) if self._in_main_menu: ba.app.ui.set_main_menu_window( - AccountSettingsWindow(transition='in_left').get_root_widget()) + AccountSettingsWindow(transition='in_left').get_root_widget() + ) # If we're being called up standalone, handle pause/resume ourself. else: @@ -270,9 +330,12 @@ class ProfileBrowserWindow(ba.Window): def _refresh(self) -> None: # pylint: disable=too-many-locals from efro.util import asserttype - from ba.internal import (PlayerProfilesChangedMessage, - get_player_profile_colors, - get_player_profile_icon) + from ba.internal import ( + PlayerProfilesChangedMessage, + get_player_profile_colors, + get_player_profile_icon, + ) + old_selection = self._selected_profile # Delete old. @@ -294,8 +357,11 @@ class ProfileBrowserWindow(ba.Window): continue color, _highlight = get_player_profile_colors(p_name) scl = 1.1 - tval = (account_name if p_name == '__account__' else - get_player_profile_icon(p_name) + p_name) + tval = ( + account_name + if p_name == '__account__' + else get_player_profile_icon(p_name) + p_name + ) assert isinstance(tval, str) txtw = ba.textwidget( parent=self._columnwidget, @@ -310,7 +376,8 @@ class ProfileBrowserWindow(ba.Window): color=ba.safecolor(color, 0.4), always_highlight=True, on_activate_call=ba.Call(self._edit_button.activate), - selectable=True) + selectable=True, + ) if index == 0: ba.widget(edit=txtw, up_widget=self._back_button) ba.widget(edit=txtw, show_buffer_top=40, show_buffer_bottom=40) @@ -325,9 +392,11 @@ class ProfileBrowserWindow(ba.Window): index += 1 if widget_to_select is not None: - ba.columnwidget(edit=self._columnwidget, - selected_child=widget_to_select, - visible_child=widget_to_select) + ba.columnwidget( + edit=self._columnwidget, + selected_child=widget_to_select, + visible_child=widget_to_select, + ) # If there's a team-chooser in existence, tell it the profile-list # has probably changed. diff --git a/dist/ba_data/python/bastd/ui/profile/edit.py b/dist/ba_data/python/bastd/ui/profile/edit.py index 3304d1c..2ebb162 100644 --- a/dist/ba_data/python/bastd/ui/profile/edit.py +++ b/dist/ba_data/python/bastd/ui/profile/edit.py @@ -22,18 +22,23 @@ class EditProfileWindow(ba.Window): """Transitions out and recreates ourself.""" ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.ui.set_main_menu_window( - EditProfileWindow(self.getname(), - self._in_main_menu).get_root_widget()) + EditProfileWindow( + self.getname(), self._in_main_menu + ).get_root_widget() + ) - def __init__(self, - existing_profile: str | None, - in_main_menu: bool, - transition: str = 'in_right'): + def __init__( + self, + existing_profile: str | None, + in_main_menu: bool, + transition: str = 'in_right', + ): # FIXME: Tidy this up a bit. # pylint: disable=too-many-branches # pylint: disable=too-many-statements # pylint: disable=too-many-locals from ba.internal import get_player_profile_colors + self._in_main_menu = in_main_menu self._existing_profile = existing_profile self._r = 'editProfileWindow' @@ -43,22 +48,35 @@ class EditProfileWindow(ba.Window): # Grab profile colors or pick random ones. self._color, self._highlight = get_player_profile_colors( - existing_profile) + existing_profile + ) uiscale = ba.app.ui.uiscale self._width = width = 780.0 if uiscale is ba.UIScale.SMALL else 680.0 self._x_inset = x_inset = 50.0 if uiscale is ba.UIScale.SMALL else 0.0 self._height = height = ( - 350.0 if uiscale is ba.UIScale.SMALL else - 400.0 if uiscale is ba.UIScale.MEDIUM else 450.0) + 350.0 + if uiscale is ba.UIScale.SMALL + else 400.0 + if uiscale is ba.UIScale.MEDIUM + else 450.0 + ) spacing = 40 - self._base_scale = (2.05 if uiscale is ba.UIScale.SMALL else - 1.5 if uiscale is ba.UIScale.MEDIUM else 1.0) + self._base_scale = ( + 2.05 + if uiscale is ba.UIScale.SMALL + else 1.5 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ) top_extra = 15 if uiscale is ba.UIScale.SMALL else 15 - super().__init__(root_widget=ba.containerwidget( - size=(width, height + top_extra), - transition=transition, - scale=self._base_scale, - stack_offset=(0, 15) if uiscale is ba.UIScale.SMALL else (0, 0))) + super().__init__( + root_widget=ba.containerwidget( + size=(width, height + top_extra), + transition=transition, + scale=self._base_scale, + stack_offset=(0, 15) if uiscale is ba.UIScale.SMALL else (0, 0), + ) + ) cancel_button = btn = ba.buttonwidget( parent=self._root_widget, position=(52 + x_inset, height - 60), @@ -66,34 +84,41 @@ class EditProfileWindow(ba.Window): scale=0.8, autoselect=True, label=ba.Lstr(resource='cancelText'), - on_activate_call=self._cancel) + on_activate_call=self._cancel, + ) ba.containerwidget(edit=self._root_widget, cancel_button=btn) - save_button = btn = ba.buttonwidget(parent=self._root_widget, - position=(width - (177 + x_inset), - height - 60), - size=(155, 60), - autoselect=True, - scale=0.8, - label=ba.Lstr(resource='saveText')) + save_button = btn = ba.buttonwidget( + parent=self._root_widget, + position=(width - (177 + x_inset), height - 60), + size=(155, 60), + autoselect=True, + scale=0.8, + label=ba.Lstr(resource='saveText'), + ) ba.widget(edit=save_button, left_widget=cancel_button) ba.widget(edit=cancel_button, right_widget=save_button) ba.containerwidget(edit=self._root_widget, start_button=btn) - ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, height - 38), - size=(0, 0), - text=(ba.Lstr(resource=self._r + '.titleNewText') - if existing_profile is None else ba.Lstr( - resource=self._r + '.titleEditText')), - color=ba.app.ui.title_color, - maxwidth=290, - scale=1.0, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, height - 38), + size=(0, 0), + text=( + ba.Lstr(resource=self._r + '.titleNewText') + if existing_profile is None + else ba.Lstr(resource=self._r + '.titleEditText') + ), + color=ba.app.ui.title_color, + maxwidth=290, + scale=1.0, + h_align='center', + v_align='center', + ) # Make a list of spaz icons. self.refresh_characters() - profile = ba.app.config.get('Player Profiles', - {}).get(self._existing_profile, {}) + profile = ba.app.config.get('Player Profiles', {}).get( + self._existing_profile, {} + ) if 'global' in profile: self._global = profile['global'] @@ -126,19 +151,22 @@ class EditProfileWindow(ba.Window): ba.buttonwidget(edit=save_button, on_activate_call=self.save) v = height - 115.0 - self._name = ('' if self._existing_profile is None else - self._existing_profile) - self._is_account_profile = (self._name == '__account__') + self._name = ( + '' if self._existing_profile is None else self._existing_profile + ) + self._is_account_profile = self._name == '__account__' # If we just picked a random character, see if it has specific # colors/highlights associated with it and assign them if so. if assigned_random_char: clr = ba.app.spaz_appearances[ - self._spazzes[icon_index]].default_color + self._spazzes[icon_index] + ].default_color if clr is not None: self._color = clr highlight = ba.app.spaz_appearances[ - self._spazzes[icon_index]].default_highlight + self._spazzes[icon_index] + ].default_highlight if highlight is not None: self._highlight = highlight @@ -147,28 +175,31 @@ class EditProfileWindow(ba.Window): names = ba.internal.get_random_names() self._name = names[random.randrange(len(names))] - self._clipped_name_text = ba.textwidget(parent=self._root_widget, - text='', - position=(540 + x_inset, - v - 8), - flatness=1.0, - shadow=0.0, - scale=0.55, - size=(0, 0), - maxwidth=100, - h_align='center', - v_align='center', - color=(1, 1, 0, 0.5)) + self._clipped_name_text = ba.textwidget( + parent=self._root_widget, + text='', + position=(540 + x_inset, v - 8), + flatness=1.0, + shadow=0.0, + scale=0.55, + size=(0, 0), + maxwidth=100, + h_align='center', + v_align='center', + color=(1, 1, 0, 0.5), + ) if not self._is_account_profile and not self._global: - ba.textwidget(parent=self._root_widget, - text=ba.Lstr(resource=self._r + '.nameText'), - position=(200 + x_inset, v - 6), - size=(0, 0), - h_align='right', - v_align='center', - color=(1, 1, 1, 0.5), - scale=0.9) + ba.textwidget( + parent=self._root_widget, + text=ba.Lstr(resource=self._r + '.nameText'), + position=(200 + x_inset, v - 6), + size=(0, 0), + h_align='right', + v_align='center', + color=(1, 1, 1, 0.5), + scale=0.9, + ) self._upgrade_button = None if self._is_account_profile: @@ -176,29 +207,34 @@ class EditProfileWindow(ba.Window): sval = ba.internal.get_v1_account_display_string() else: sval = '??' - ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, v - 7), - size=(0, 0), - scale=1.2, - text=sval, - maxwidth=270, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, v - 7), + size=(0, 0), + scale=1.2, + text=sval, + maxwidth=270, + h_align='center', + v_align='center', + ) txtl = ba.Lstr( - resource='editProfileWindow.accountProfileText').evaluate() + resource='editProfileWindow.accountProfileText' + ).evaluate() b_width = min( 270.0, - ba.internal.get_string_width(txtl, suppress_warning=True) * - 0.6) - ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, v - 39), - size=(0, 0), - scale=0.6, - color=ba.app.ui.infotextcolor, - text=txtl, - maxwidth=270, - h_align='center', - v_align='center') + ba.internal.get_string_width(txtl, suppress_warning=True) * 0.6, + ) + ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, v - 39), + size=(0, 0), + scale=0.6, + color=ba.app.ui.infotextcolor, + text=txtl, + maxwidth=270, + h_align='center', + v_align='center', + ) self._account_type_info_button = ba.buttonwidget( parent=self._root_widget, label='?', @@ -208,7 +244,8 @@ class EditProfileWindow(ba.Window): button_type='square', color=(0.6, 0.5, 0.65), autoselect=True, - on_activate_call=self.show_account_profile_info) + on_activate_call=self.show_account_profile_info, + ) elif self._global: b_size = 60 @@ -221,7 +258,8 @@ class EditProfileWindow(ba.Window): label='', button_type='square', text_scale=1.2, - on_activate_call=self._on_icon_press) + on_activate_call=self._on_icon_press, + ) self._icon_button_label = ba.textwidget( parent=self._root_widget, position=(self._width * 0.5 - 160, v - 35), @@ -231,45 +269,53 @@ class EditProfileWindow(ba.Window): size=(0, 0), color=(1, 1, 1), text='', - scale=2.0) + scale=2.0, + ) - ba.textwidget(parent=self._root_widget, - h_align='center', - v_align='center', - position=(self._width * 0.5 - 160, v - 55 - 15), - size=(0, 0), - draw_controller=btn, - text=ba.Lstr(resource=self._r + '.iconText'), - scale=0.7, - color=ba.app.ui.title_color, - maxwidth=120) + ba.textwidget( + parent=self._root_widget, + h_align='center', + v_align='center', + position=(self._width * 0.5 - 160, v - 55 - 15), + size=(0, 0), + draw_controller=btn, + text=ba.Lstr(resource=self._r + '.iconText'), + scale=0.7, + color=ba.app.ui.title_color, + maxwidth=120, + ) self._update_icon() - ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, v - 7), - size=(0, 0), - scale=1.2, - text=self._name, - maxwidth=240, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, v - 7), + size=(0, 0), + scale=1.2, + text=self._name, + maxwidth=240, + h_align='center', + v_align='center', + ) # FIXME hard coded strings are bad txtl = ba.Lstr( - resource='editProfileWindow.globalProfileText').evaluate() + resource='editProfileWindow.globalProfileText' + ).evaluate() b_width = min( 240.0, - ba.internal.get_string_width(txtl, suppress_warning=True) * - 0.6) - ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, v - 39), - size=(0, 0), - scale=0.6, - color=ba.app.ui.infotextcolor, - text=txtl, - maxwidth=240, - h_align='center', - v_align='center') + ba.internal.get_string_width(txtl, suppress_warning=True) * 0.6, + ) + ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, v - 39), + size=(0, 0), + scale=0.6, + color=ba.app.ui.infotextcolor, + text=txtl, + maxwidth=240, + h_align='center', + v_align='center', + ) self._account_type_info_button = ba.buttonwidget( parent=self._root_widget, label='?', @@ -279,7 +325,8 @@ class EditProfileWindow(ba.Window): button_type='square', color=(0.6, 0.5, 0.65), autoselect=True, - on_activate_call=self.show_global_profile_info) + on_activate_call=self.show_global_profile_info, + ) else: self._text_field = ba.textwidget( parent=self._root_widget, @@ -294,24 +341,28 @@ class EditProfileWindow(ba.Window): editable=True, padding=4, color=(0.9, 0.9, 0.9, 1.0), - on_return_press_call=ba.Call(save_button.activate)) + on_return_press_call=ba.Call(save_button.activate), + ) # FIXME hard coded strings are bad txtl = ba.Lstr( - resource='editProfileWindow.localProfileText').evaluate() + resource='editProfileWindow.localProfileText' + ).evaluate() b_width = min( 270.0, - ba.internal.get_string_width(txtl, suppress_warning=True) * - 0.6) - ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, v - 43), - size=(0, 0), - scale=0.6, - color=ba.app.ui.infotextcolor, - text=txtl, - maxwidth=270, - h_align='center', - v_align='center') + ba.internal.get_string_width(txtl, suppress_warning=True) * 0.6, + ) + ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, v - 43), + size=(0, 0), + scale=0.6, + color=ba.app.ui.infotextcolor, + text=txtl, + maxwidth=270, + h_align='center', + v_align='center', + ) self._account_type_info_button = ba.buttonwidget( parent=self._root_widget, label='?', @@ -321,7 +372,8 @@ class EditProfileWindow(ba.Window): button_type='square', color=(0.6, 0.5, 0.65), autoselect=True, - on_activate_call=self.show_local_profile_info) + on_activate_call=self.show_local_profile_info, + ) self._upgrade_button = ba.buttonwidget( parent=self._root_widget, label=ba.Lstr(resource='upgradeText'), @@ -331,14 +383,16 @@ class EditProfileWindow(ba.Window): position=(self._width * 0.5 + b_width * 0.5 + 13 + 43, v - 51), color=(0.6, 0.5, 0.65), autoselect=True, - on_activate_call=self.upgrade_profile) + on_activate_call=self.upgrade_profile, + ) self._update_clipped_name() - self._clipped_name_timer = ba.Timer(0.333, - ba.WeakCall( - self._update_clipped_name), - timetype=ba.TimeType.REAL, - repeat=True) + self._clipped_name_timer = ba.Timer( + 0.333, + ba.WeakCall(self._update_clipped_name), + timetype=ba.TimeType.REAL, + repeat=True, + ) v -= spacing * 3.0 b_size = 80 @@ -351,21 +405,25 @@ class EditProfileWindow(ba.Window): size=(b_size, b_size), color=self._color, label='', - button_type='square') + button_type='square', + ) origin = self._color_button.get_screen_space_center() - ba.buttonwidget(edit=self._color_button, - on_activate_call=ba.WeakCall(self._make_picker, - 'color', origin)) - ba.textwidget(parent=self._root_widget, - h_align='center', - v_align='center', - position=(self._width * 0.5 - b_offs, v - 65), - size=(0, 0), - draw_controller=btn, - text=ba.Lstr(resource=self._r + '.colorText'), - scale=0.7, - color=ba.app.ui.title_color, - maxwidth=120) + ba.buttonwidget( + edit=self._color_button, + on_activate_call=ba.WeakCall(self._make_picker, 'color', origin), + ) + ba.textwidget( + parent=self._root_widget, + h_align='center', + v_align='center', + position=(self._width * 0.5 - b_offs, v - 65), + size=(0, 0), + draw_controller=btn, + text=ba.Lstr(resource=self._r + '.colorText'), + scale=0.7, + color=ba.app.ui.title_color, + maxwidth=120, + ) self._character_button = btn = ba.buttonwidget( parent=self._root_widget, @@ -376,59 +434,73 @@ class EditProfileWindow(ba.Window): size=(b_size_2, b_size_2), label='', color=(1, 1, 1), - mask_texture=ba.gettexture('characterIconMask')) + mask_texture=ba.gettexture('characterIconMask'), + ) if not self._is_account_profile and not self._global: - ba.containerwidget(edit=self._root_widget, - selected_child=self._text_field) - ba.textwidget(parent=self._root_widget, - h_align='center', - v_align='center', - position=(self._width * 0.5, v - 80), - size=(0, 0), - draw_controller=btn, - text=ba.Lstr(resource=self._r + '.characterText'), - scale=0.7, - color=ba.app.ui.title_color, - maxwidth=130) + ba.containerwidget( + edit=self._root_widget, selected_child=self._text_field + ) + ba.textwidget( + parent=self._root_widget, + h_align='center', + v_align='center', + position=(self._width * 0.5, v - 80), + size=(0, 0), + draw_controller=btn, + text=ba.Lstr(resource=self._r + '.characterText'), + scale=0.7, + color=ba.app.ui.title_color, + maxwidth=130, + ) self._highlight_button = btn = ba.buttonwidget( parent=self._root_widget, autoselect=True, position=(self._width * 0.5 + b_offs - b_size * 0.5, v - 50), - up_widget=self._upgrade_button if self._upgrade_button is not None + up_widget=self._upgrade_button + if self._upgrade_button is not None else self._account_type_info_button, size=(b_size, b_size), color=self._highlight, label='', - button_type='square') + button_type='square', + ) if not self._is_account_profile and not self._global: ba.widget(edit=cancel_button, down_widget=self._text_field) ba.widget(edit=save_button, down_widget=self._text_field) ba.widget(edit=self._color_button, up_widget=self._text_field) - ba.widget(edit=self._account_type_info_button, - down_widget=self._character_button) + ba.widget( + edit=self._account_type_info_button, + down_widget=self._character_button, + ) origin = self._highlight_button.get_screen_space_center() - ba.buttonwidget(edit=self._highlight_button, - on_activate_call=ba.WeakCall(self._make_picker, - 'highlight', origin)) - ba.textwidget(parent=self._root_widget, - h_align='center', - v_align='center', - position=(self._width * 0.5 + b_offs, v - 65), - size=(0, 0), - draw_controller=btn, - text=ba.Lstr(resource=self._r + '.highlightText'), - scale=0.7, - color=ba.app.ui.title_color, - maxwidth=120) + ba.buttonwidget( + edit=self._highlight_button, + on_activate_call=ba.WeakCall( + self._make_picker, 'highlight', origin + ), + ) + ba.textwidget( + parent=self._root_widget, + h_align='center', + v_align='center', + position=(self._width * 0.5 + b_offs, v - 65), + size=(0, 0), + draw_controller=btn, + text=ba.Lstr(resource=self._r + '.highlightText'), + scale=0.7, + color=ba.app.ui.title_color, + maxwidth=120, + ) self._update_character() def upgrade_profile(self) -> None: """Attempt to ugrade the profile to global.""" from bastd.ui import account from bastd.ui.profile import upgrade as pupgrade + if ba.internal.get_v1_account_state() != 'signed_in': account.show_sign_in_prompt() return @@ -438,46 +510,64 @@ class EditProfileWindow(ba.Window): def show_account_profile_info(self) -> None: """Show an explanation of account profiles.""" from bastd.ui.confirm import ConfirmWindow - icons_str = ' '.join([ - ba.charstr(n) for n in [ - ba.SpecialChar.GOOGLE_PLAY_GAMES_LOGO, - ba.SpecialChar.GAME_CENTER_LOGO, - ba.SpecialChar.GAME_CIRCLE_LOGO, ba.SpecialChar.OUYA_LOGO, - ba.SpecialChar.LOCAL_ACCOUNT, ba.SpecialChar.ALIBABA_LOGO, - ba.SpecialChar.OCULUS_LOGO, ba.SpecialChar.NVIDIA_LOGO + + icons_str = ' '.join( + [ + ba.charstr(n) + for n in [ + ba.SpecialChar.GOOGLE_PLAY_GAMES_LOGO, + ba.SpecialChar.GAME_CENTER_LOGO, + ba.SpecialChar.GAME_CIRCLE_LOGO, + ba.SpecialChar.OUYA_LOGO, + ba.SpecialChar.LOCAL_ACCOUNT, + ba.SpecialChar.ALIBABA_LOGO, + ba.SpecialChar.OCULUS_LOGO, + ba.SpecialChar.NVIDIA_LOGO, + ] ] - ]) - txtl = ba.Lstr(resource='editProfileWindow.accountProfileInfoText', - subs=[('${ICONS}', icons_str)]) - ConfirmWindow(txtl, - cancel_button=False, - width=500, - height=300, - origin_widget=self._account_type_info_button) + ) + txtl = ba.Lstr( + resource='editProfileWindow.accountProfileInfoText', + subs=[('${ICONS}', icons_str)], + ) + ConfirmWindow( + txtl, + cancel_button=False, + width=500, + height=300, + origin_widget=self._account_type_info_button, + ) def show_local_profile_info(self) -> None: """Show an explanation of local profiles.""" from bastd.ui.confirm import ConfirmWindow + txtl = ba.Lstr(resource='editProfileWindow.localProfileInfoText') - ConfirmWindow(txtl, - cancel_button=False, - width=600, - height=250, - origin_widget=self._account_type_info_button) + ConfirmWindow( + txtl, + cancel_button=False, + width=600, + height=250, + origin_widget=self._account_type_info_button, + ) def show_global_profile_info(self) -> None: """Show an explanation of global profiles.""" from bastd.ui.confirm import ConfirmWindow + txtl = ba.Lstr(resource='editProfileWindow.globalProfileInfoText') - ConfirmWindow(txtl, - cancel_button=False, - width=600, - height=250, - origin_widget=self._account_type_info_button) + ConfirmWindow( + txtl, + cancel_button=False, + width=600, + height=250, + origin_widget=self._account_type_info_button, + ) def refresh_characters(self) -> None: """Refresh available characters/icons.""" from bastd.actor import spazappearance + self._spazzes = spazappearance.get_appearances() self._spazzes.sort() self._icon_textures = [ @@ -501,33 +591,40 @@ class EditProfileWindow(ba.Window): # The player could have bought a new one while the picker was up. self.refresh_characters() - self._icon_index = self._spazzes.index( - character) if character in self._spazzes else 0 + self._icon_index = ( + self._spazzes.index(character) if character in self._spazzes else 0 + ) self._update_character() def _on_character_press(self) -> None: from bastd.ui import characterpicker + characterpicker.CharacterPicker( parent=self._root_widget, position=self._character_button.get_screen_space_center(), selected_character=self._spazzes[self._icon_index], delegate=self, tint_color=self._color, - tint2_color=self._highlight) + tint2_color=self._highlight, + ) def _on_icon_press(self) -> None: from bastd.ui import iconpicker + iconpicker.IconPicker( parent=self._root_widget, position=self._icon_button.get_screen_space_center(), selected_icon=self._icon, delegate=self, tint_color=self._color, - tint2_color=self._highlight) + tint2_color=self._highlight, + ) - def _make_picker(self, picker_type: str, origin: tuple[float, - float]) -> None: + def _make_picker( + self, picker_type: str, origin: tuple[float, float] + ) -> None: from bastd.ui import colorpicker + if picker_type == 'color': initial_color = self._color elif picker_type == 'highlight': @@ -537,20 +634,26 @@ class EditProfileWindow(ba.Window): colorpicker.ColorPicker( parent=self._root_widget, position=origin, - offset=(self._base_scale * - (-100 if picker_type == 'color' else 100), 0), + offset=( + self._base_scale * (-100 if picker_type == 'color' else 100), + 0, + ), initial_color=initial_color, delegate=self, - tag=picker_type) + tag=picker_type, + ) def _cancel(self) -> None: from bastd.ui.profile.browser import ProfileBrowserWindow + ba.containerwidget(edit=self._root_widget, transition='out_right') ba.app.ui.set_main_menu_window( ProfileBrowserWindow( 'in_left', selected_profile=self._existing_profile, - in_main_menu=self._in_main_menu).get_root_widget()) + in_main_menu=self._in_main_menu, + ).get_root_widget() + ) def _set_color(self, color: tuple[float, float, float]) -> None: self._color = color @@ -568,16 +671,19 @@ class EditProfileWindow(ba.Window): return tag = picker.get_tag() if tag == 'color': - ba.containerwidget(edit=self._root_widget, - selected_child=self._color_button) + ba.containerwidget( + edit=self._root_widget, selected_child=self._color_button + ) elif tag == 'highlight': - ba.containerwidget(edit=self._root_widget, - selected_child=self._highlight_button) + ba.containerwidget( + edit=self._root_widget, selected_child=self._highlight_button + ) else: print('color_picker_closing got unknown tag ' + str(tag)) - def color_picker_selected_color(self, picker: ColorPicker, - color: tuple[float, float, float]) -> None: + def color_picker_selected_color( + self, picker: ColorPicker, color: tuple[float, float, float] + ) -> None: """Called when a color is selected in a color picker.""" if not self._root_widget: return @@ -595,13 +701,19 @@ class EditProfileWindow(ba.Window): return name = self.getname() if name == '__account__': - name = (ba.internal.get_v1_account_name() - if ba.internal.get_v1_account_state() == 'signed_in' else - '???') + name = ( + ba.internal.get_v1_account_name() + if ba.internal.get_v1_account_state() == 'signed_in' + else '???' + ) if len(name) > 10 and not (self._global or self._is_account_profile): - ba.textwidget(edit=self._clipped_name_text, - text=ba.Lstr(resource='inGameClippedNameText', - subs=[('${NAME}', name[:10] + '...')])) + ba.textwidget( + edit=self._clipped_name_text, + text=ba.Lstr( + resource='inGameClippedNameText', + subs=[('${NAME}', name[:10] + '...')], + ), + ) else: ba.textwidget(edit=self._clipped_name_text, text='') @@ -613,7 +725,8 @@ class EditProfileWindow(ba.Window): texture=self._icon_textures[self._icon_index], tint_texture=self._icon_tint_textures[self._icon_index], tint_color=self._color, - tint2_color=self._highlight) + tint2_color=self._highlight, + ) def _update_icon(self) -> None: if self._icon_button_label: @@ -632,6 +745,7 @@ class EditProfileWindow(ba.Window): def save(self, transition_out: bool = True) -> bool: """Save has been selected.""" from bastd.ui.profile.browser import ProfileBrowserWindow + new_name = self.getname().strip() if not new_name: @@ -644,26 +758,30 @@ class EditProfileWindow(ba.Window): # Delete old in case we're renaming. if self._existing_profile and self._existing_profile != new_name: - ba.internal.add_transaction({ - 'type': 'REMOVE_PLAYER_PROFILE', - 'name': self._existing_profile - }) + ba.internal.add_transaction( + { + 'type': 'REMOVE_PLAYER_PROFILE', + 'name': self._existing_profile, + } + ) # Also lets be aware we're no longer global if we're taking a # new name (will need to re-request it). self._global = False - ba.internal.add_transaction({ - 'type': 'ADD_PLAYER_PROFILE', - 'name': new_name, - 'profile': { - 'character': self._spazzes[self._icon_index], - 'color': list(self._color), - 'global': self._global, - 'icon': self._icon, - 'highlight': list(self._highlight) + ba.internal.add_transaction( + { + 'type': 'ADD_PLAYER_PROFILE', + 'name': new_name, + 'profile': { + 'character': self._spazzes[self._icon_index], + 'color': list(self._color), + 'global': self._global, + 'icon': self._icon, + 'highlight': list(self._highlight), + }, } - }) + ) if transition_out: ba.internal.run_transactions() @@ -672,5 +790,7 @@ class EditProfileWindow(ba.Window): ProfileBrowserWindow( 'in_left', selected_profile=new_name, - in_main_menu=self._in_main_menu).get_root_widget()) + in_main_menu=self._in_main_menu, + ).get_root_widget() + ) return True diff --git a/dist/ba_data/python/bastd/ui/profile/upgrade.py b/dist/ba_data/python/bastd/ui/profile/upgrade.py index ffce43e..e4a30f6 100644 --- a/dist/ba_data/python/bastd/ui/profile/upgrade.py +++ b/dist/ba_data/python/bastd/ui/profile/upgrade.py @@ -19,35 +19,48 @@ if TYPE_CHECKING: class ProfileUpgradeWindow(ba.Window): """Window for player profile upgrades to global.""" - def __init__(self, - edit_profile_window: EditProfileWindow, - transition: str = 'in_right'): + def __init__( + self, + edit_profile_window: EditProfileWindow, + transition: str = 'in_right', + ): from ba.internal import master_server_get + self._r = 'editProfileWindow' self._width = 680 self._height = 350 uiscale = ba.app.ui.uiscale - self._base_scale = (2.05 if uiscale is ba.UIScale.SMALL else - 1.5 if uiscale is ba.UIScale.MEDIUM else 1.2) + self._base_scale = ( + 2.05 + if uiscale is ba.UIScale.SMALL + else 1.5 + if uiscale is ba.UIScale.MEDIUM + else 1.2 + ) self._upgrade_start_time: float | None = None self._name = edit_profile_window.getname() self._edit_profile_window = weakref.ref(edit_profile_window) top_extra = 15 if uiscale is ba.UIScale.SMALL else 15 - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height + top_extra), - toolbar_visibility='menu_currency', - transition=transition, - scale=self._base_scale, - stack_offset=(0, 15) if uiscale is ba.UIScale.SMALL else (0, 0))) - cancel_button = ba.buttonwidget(parent=self._root_widget, - position=(52, 30), - size=(155, 60), - scale=0.8, - autoselect=True, - label=ba.Lstr(resource='cancelText'), - on_activate_call=self._cancel) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height + top_extra), + toolbar_visibility='menu_currency', + transition=transition, + scale=self._base_scale, + stack_offset=(0, 15) if uiscale is ba.UIScale.SMALL else (0, 0), + ) + ) + cancel_button = ba.buttonwidget( + parent=self._root_widget, + position=(52, 30), + size=(155, 60), + scale=0.8, + autoselect=True, + label=ba.Lstr(resource='cancelText'), + on_activate_call=self._cancel, + ) self._upgrade_button = ba.buttonwidget( parent=self._root_widget, position=(self._width - 190, 30), @@ -55,56 +68,65 @@ class ProfileUpgradeWindow(ba.Window): scale=0.8, autoselect=True, label=ba.Lstr(resource='upgradeText'), - on_activate_call=self._on_upgrade_press) - ba.containerwidget(edit=self._root_widget, - cancel_button=cancel_button, - start_button=self._upgrade_button, - selected_child=self._upgrade_button) + on_activate_call=self._on_upgrade_press, + ) + ba.containerwidget( + edit=self._root_widget, + cancel_button=cancel_button, + start_button=self._upgrade_button, + selected_child=self._upgrade_button, + ) - ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, self._height - 38), - size=(0, 0), - text=ba.Lstr(resource=self._r + - '.upgradeToGlobalProfileText'), - color=ba.app.ui.title_color, - maxwidth=self._width * 0.45, - scale=1.0, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, self._height - 38), + size=(0, 0), + text=ba.Lstr(resource=self._r + '.upgradeToGlobalProfileText'), + color=ba.app.ui.title_color, + maxwidth=self._width * 0.45, + scale=1.0, + h_align='center', + v_align='center', + ) - ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, self._height - 100), - size=(0, 0), - text=ba.Lstr(resource=self._r + - '.upgradeProfileInfoText'), - color=ba.app.ui.infotextcolor, - maxwidth=self._width * 0.8, - scale=0.7, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, self._height - 100), + size=(0, 0), + text=ba.Lstr(resource=self._r + '.upgradeProfileInfoText'), + color=ba.app.ui.infotextcolor, + maxwidth=self._width * 0.8, + scale=0.7, + h_align='center', + v_align='center', + ) self._status_text = ba.textwidget( parent=self._root_widget, position=(self._width * 0.5, self._height - 160), size=(0, 0), - text=ba.Lstr(resource=self._r + '.checkingAvailabilityText', - subs=[('${NAME}', self._name)]), + text=ba.Lstr( + resource=self._r + '.checkingAvailabilityText', + subs=[('${NAME}', self._name)], + ), color=(0.8, 0.4, 0.0), maxwidth=self._width * 0.8, scale=0.65, h_align='center', - v_align='center') + v_align='center', + ) - self._price_text = ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, - self._height - 230), - size=(0, 0), - text='', - color=(0.2, 1, 0.2), - maxwidth=self._width * 0.8, - scale=1.5, - h_align='center', - v_align='center') + self._price_text = ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, self._height - 230), + size=(0, 0), + text='', + color=(0.2, 1, 0.2), + maxwidth=self._width * 0.8, + scale=1.5, + h_align='center', + v_align='center', + ) self._tickets_text: ba.Widget | None if not ba.app.ui.use_toolbars: @@ -117,22 +139,26 @@ class ProfileUpgradeWindow(ba.Window): maxwidth=100, scale=0.5, h_align='right', - v_align='center') + v_align='center', + ) else: self._tickets_text = None - master_server_get('bsGlobalProfileCheck', { - 'name': self._name, - 'b': ba.app.build_number - }, - callback=ba.WeakCall(self._profile_check_result)) + master_server_get( + 'bsGlobalProfileCheck', + {'name': self._name, 'b': ba.app.build_number}, + callback=ba.WeakCall(self._profile_check_result), + ) self._cost = ba.internal.get_v1_account_misc_read_val( - 'price.global_profile', 500) + 'price.global_profile', 500 + ) self._status: str | None = 'waiting' - self._update_timer = ba.Timer(1.0, - ba.WeakCall(self._update), - timetype=ba.TimeType.REAL, - repeat=True) + self._update_timer = ba.Timer( + 1.0, + ba.WeakCall(self._update), + timetype=ba.TimeType.REAL, + repeat=True, + ) self._update() def _profile_check_result(self, result: dict[str, Any] | None) -> None: @@ -140,34 +166,48 @@ class ProfileUpgradeWindow(ba.Window): ba.textwidget( edit=self._status_text, text=ba.Lstr(resource='internal.unavailableNoConnectionText'), - color=(1, 0, 0)) + color=(1, 0, 0), + ) self._status = 'error' - ba.buttonwidget(edit=self._upgrade_button, - color=(0.4, 0.4, 0.4), - textcolor=(0.5, 0.5, 0.5)) + ba.buttonwidget( + edit=self._upgrade_button, + color=(0.4, 0.4, 0.4), + textcolor=(0.5, 0.5, 0.5), + ) else: if result['available']: - ba.textwidget(edit=self._status_text, - text=ba.Lstr(resource=self._r + '.availableText', - subs=[('${NAME}', self._name)]), - color=(0, 1, 0)) - ba.textwidget(edit=self._price_text, - text=ba.charstr(ba.SpecialChar.TICKET) + - str(self._cost)) + ba.textwidget( + edit=self._status_text, + text=ba.Lstr( + resource=self._r + '.availableText', + subs=[('${NAME}', self._name)], + ), + color=(0, 1, 0), + ) + ba.textwidget( + edit=self._price_text, + text=ba.charstr(ba.SpecialChar.TICKET) + str(self._cost), + ) self._status = None else: - ba.textwidget(edit=self._status_text, - text=ba.Lstr(resource=self._r + - '.unavailableText', - subs=[('${NAME}', self._name)]), - color=(1, 0, 0)) + ba.textwidget( + edit=self._status_text, + text=ba.Lstr( + resource=self._r + '.unavailableText', + subs=[('${NAME}', self._name)], + ), + color=(1, 0, 0), + ) self._status = 'unavailable' - ba.buttonwidget(edit=self._upgrade_button, - color=(0.4, 0.4, 0.4), - textcolor=(0.5, 0.5, 0.5)) + ba.buttonwidget( + edit=self._upgrade_button, + color=(0.4, 0.4, 0.4), + textcolor=(0.5, 0.5, 0.5), + ) def _on_upgrade_press(self) -> None: from bastd.ui import getcurrency + if self._status is None: # If it appears we don't have enough tickets, offer to buy more. tickets = ba.internal.get_v1_account_ticket_count() @@ -175,8 +215,9 @@ class ProfileUpgradeWindow(ba.Window): ba.playsound(ba.getsound('error')) getcurrency.show_get_tickets_prompt() return - ba.screenmessage(ba.Lstr(resource='purchasingText'), - color=(0, 1, 0)) + ba.screenmessage( + ba.Lstr(resource='purchasingText'), color=(0, 1, 0) + ) self._status = 'pre_upgrading' # Now we tell the original editor to save the profile, add an @@ -189,14 +230,12 @@ class ProfileUpgradeWindow(ba.Window): success = edit_profile_window.save(transition_out=False) if not success: print('profile upgrade: error occurred saving profile') - ba.screenmessage(ba.Lstr(resource='errorText'), - color=(1, 0, 0)) + ba.screenmessage(ba.Lstr(resource='errorText'), color=(1, 0, 0)) ba.playsound(ba.getsound('error')) return - ba.internal.add_transaction({ - 'type': 'UPGRADE_PROFILE', - 'name': self._name - }) + ba.internal.add_transaction( + {'type': 'UPGRADE_PROFILE', 'name': self._name} + ) ba.internal.run_transactions() self._status = 'upgrading' self._upgrade_start_time = time.time() @@ -209,23 +248,30 @@ class ProfileUpgradeWindow(ba.Window): except Exception: t_str = '?' if self._tickets_text is not None: - ba.textwidget(edit=self._tickets_text, - text=ba.Lstr( - resource='getTicketsWindow.youHaveShortText', - subs=[('${COUNT}', - ba.charstr(ba.SpecialChar.TICKET) + t_str) - ])) + ba.textwidget( + edit=self._tickets_text, + text=ba.Lstr( + resource='getTicketsWindow.youHaveShortText', + subs=[ + ('${COUNT}', ba.charstr(ba.SpecialChar.TICKET) + t_str) + ], + ), + ) # Once we've kicked off an upgrade attempt and all transactions go # through, we're done. - if (self._status == 'upgrading' - and not ba.internal.have_outstanding_transactions()): + if ( + self._status == 'upgrading' + and not ba.internal.have_outstanding_transactions() + ): self._status = 'exiting' ba.containerwidget(edit=self._root_widget, transition='out_right') edit_profile_window = self._edit_profile_window() if edit_profile_window is None: - print('profile upgrade transition out:' - ' original edit window gone') + print( + 'profile upgrade transition out:' + ' original edit window gone' + ) return ba.playsound(ba.getsound('gunCocking')) edit_profile_window.reload_window() @@ -233,8 +279,10 @@ class ProfileUpgradeWindow(ba.Window): def _cancel(self) -> None: # If we recently sent out an upgrade request, disallow canceling # for a bit. - if (self._upgrade_start_time is not None - and time.time() - self._upgrade_start_time < 10.0): + if ( + self._upgrade_start_time is not None + and time.time() - self._upgrade_start_time < 10.0 + ): ba.playsound(ba.getsound('error')) return ba.containerwidget(edit=self._root_widget, transition='out_right') diff --git a/dist/ba_data/python/bastd/ui/promocode.py b/dist/ba_data/python/bastd/ui/promocode.py index 78de497..1096ec1 100644 --- a/dist/ba_data/python/bastd/ui/promocode.py +++ b/dist/ba_data/python/bastd/ui/promocode.py @@ -17,9 +17,9 @@ if TYPE_CHECKING: class PromoCodeWindow(ba.Window): """Window for entering promo codes.""" - def __init__(self, - modal: bool = False, - origin_widget: ba.Widget | None = None): + def __init__( + self, modal: bool = False, origin_widget: ba.Widget | None = None + ): scale_origin: tuple[float, float] | None if origin_widget is not None: @@ -38,31 +38,43 @@ class PromoCodeWindow(ba.Window): self._r = 'promoCodeWindow' uiscale = ba.app.ui.uiscale - super().__init__(root_widget=ba.containerwidget( - size=(width, height), - transition=transition, - toolbar_visibility='menu_minimal_no_back', - scale_origin_stack_offset=scale_origin, - scale=(2.0 if uiscale is ba.UIScale.SMALL else - 1.5 if uiscale is ba.UIScale.MEDIUM else 1.0))) + super().__init__( + root_widget=ba.containerwidget( + size=(width, height), + transition=transition, + toolbar_visibility='menu_minimal_no_back', + scale_origin_stack_offset=scale_origin, + scale=( + 2.0 + if uiscale is ba.UIScale.SMALL + else 1.5 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + ) + ) - btn = ba.buttonwidget(parent=self._root_widget, - scale=0.5, - position=(40, height - 40), - size=(60, 60), - label='', - on_activate_call=self._do_back, - autoselect=True, - color=(0.55, 0.5, 0.6), - icon=ba.gettexture('crossOut'), - iconscale=1.2) + btn = ba.buttonwidget( + parent=self._root_widget, + scale=0.5, + position=(40, height - 40), + size=(60, 60), + label='', + on_activate_call=self._do_back, + autoselect=True, + color=(0.55, 0.5, 0.6), + icon=ba.gettexture('crossOut'), + iconscale=1.2, + ) - ba.textwidget(parent=self._root_widget, - text=ba.Lstr(resource=self._r + '.codeText'), - position=(22, height - 113), - color=(0.8, 0.8, 0.8, 1.0), - size=(90, 30), - h_align='right') + ba.textwidget( + parent=self._root_widget, + text=ba.Lstr(resource=self._r + '.codeText'), + position=(22, height - 113), + color=(0.8, 0.8, 0.8, 1.0), + size=(90, 30), + h_align='right', + ) self._text_field = ba.textwidget( parent=self._root_widget, position=(125, height - 121), @@ -75,7 +87,8 @@ class PromoCodeWindow(ba.Window): description=ba.Lstr(resource=self._r + '.codeText'), editable=True, padding=4, - on_return_press_call=self._activate_enter_button) + on_return_press_call=self._activate_enter_button, + ) ba.widget(edit=btn, down_widget=self._text_field) b_width = 200 @@ -84,22 +97,29 @@ class PromoCodeWindow(ba.Window): position=(width * 0.5 - b_width * 0.5, height - 200), size=(b_width, 60), scale=1.0, - label=ba.Lstr(resource='submitText', - fallback_resource=self._r + '.enterText'), - on_activate_call=self._do_enter) - ba.containerwidget(edit=self._root_widget, - cancel_button=btn, - start_button=btn2, - selected_child=self._text_field) + label=ba.Lstr( + resource='submitText', fallback_resource=self._r + '.enterText' + ), + on_activate_call=self._do_enter, + ) + ba.containerwidget( + edit=self._root_widget, + cancel_button=btn, + start_button=btn2, + selected_child=self._text_field, + ) def _do_back(self) -> None: # pylint: disable=cyclic-import from bastd.ui.settings.advanced import AdvancedSettingsWindow - ba.containerwidget(edit=self._root_widget, - transition=self._transition_out) + + ba.containerwidget( + edit=self._root_widget, transition=self._transition_out + ) if not self._modal: ba.app.ui.set_main_menu_window( - AdvancedSettingsWindow(transition='in_left').get_root_widget()) + AdvancedSettingsWindow(transition='in_left').get_root_widget() + ) def _activate_enter_button(self) -> None: self._enter_button.activate() @@ -107,14 +127,19 @@ class PromoCodeWindow(ba.Window): def _do_enter(self) -> None: # pylint: disable=cyclic-import from bastd.ui.settings.advanced import AdvancedSettingsWindow - ba.containerwidget(edit=self._root_widget, - transition=self._transition_out) + + ba.containerwidget( + edit=self._root_widget, transition=self._transition_out + ) if not self._modal: ba.app.ui.set_main_menu_window( - AdvancedSettingsWindow(transition='in_left').get_root_widget()) - ba.internal.add_transaction({ - 'type': 'PROMO_CODE', - 'expire_time': time.time() + 5, - 'code': ba.textwidget(query=self._text_field) - }) + AdvancedSettingsWindow(transition='in_left').get_root_widget() + ) + ba.internal.add_transaction( + { + 'type': 'PROMO_CODE', + 'expire_time': time.time() + 5, + 'code': ba.textwidget(query=self._text_field), + } + ) ba.internal.run_transactions() diff --git a/dist/ba_data/python/bastd/ui/purchase.py b/dist/ba_data/python/bastd/ui/purchase.py index d28fa5a..8bcdd09 100644 --- a/dist/ba_data/python/bastd/ui/purchase.py +++ b/dist/ba_data/python/bastd/ui/purchase.py @@ -16,52 +16,75 @@ if TYPE_CHECKING: class PurchaseWindow(ba.Window): """Window for purchasing one or more items.""" - def __init__(self, - items: list[str], - transition: str = 'in_right', - header_text: ba.Lstr | None = None): + def __init__( + self, + items: list[str], + transition: str = 'in_right', + header_text: ba.Lstr | None = None, + ): from ba.internal import get_store_item_display_size from bastd.ui.store import item as storeitemui + if header_text is None: - header_text = ba.Lstr(resource='unlockThisText', - fallback_resource='unlockThisInTheStoreText') + header_text = ba.Lstr( + resource='unlockThisText', + fallback_resource='unlockThisInTheStoreText', + ) if len(items) != 1: raise ValueError('expected exactly 1 item') self._items = list(items) self._width = 580 self._height = 520 uiscale = ba.app.ui.uiscale - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height), - transition=transition, - toolbar_visibility='menu_currency', - scale=(1.2 if uiscale is ba.UIScale.SMALL else - 1.1 if uiscale is ba.UIScale.MEDIUM else 1.0), - stack_offset=(0, -15) if uiscale is ba.UIScale.SMALL else (0, 0))) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height), + transition=transition, + toolbar_visibility='menu_currency', + scale=( + 1.2 + if uiscale is ba.UIScale.SMALL + else 1.1 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + stack_offset=(0, -15) + if uiscale is ba.UIScale.SMALL + else (0, 0), + ) + ) self._is_double = False - self._title_text = ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, - self._height - 30), - size=(0, 0), - text=header_text, - h_align='center', - v_align='center', - maxwidth=self._width * 0.9 - 120, - scale=1.2, - color=(1, 0.8, 0.3, 1)) + self._title_text = ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, self._height - 30), + size=(0, 0), + text=header_text, + h_align='center', + v_align='center', + maxwidth=self._width * 0.9 - 120, + scale=1.2, + color=(1, 0.8, 0.3, 1), + ) size = get_store_item_display_size(items[0]) display: dict[str, Any] = {} storeitemui.instantiate_store_item_display( items[0], display, parent_widget=self._root_widget, - b_pos=(self._width * 0.5 - size[0] * 0.5 + 10 - - ((size[0] * 0.5 + 30) if self._is_double else 0), - self._height * 0.5 - size[1] * 0.5 + 30 + - (20 if self._is_double else 0)), + b_pos=( + self._width * 0.5 + - size[0] * 0.5 + + 10 + - ((size[0] * 0.5 + 30) if self._is_double else 0), + self._height * 0.5 + - size[1] * 0.5 + + 30 + + (20 if self._is_double else 0), + ), b_width=size[0], b_height=size[1], - button=False) + button=False, + ) # Wire up the parts we need. if self._is_double: @@ -73,23 +96,27 @@ class PurchaseWindow(ba.Window): else: pyoffs = 0 price = self._price = ba.internal.get_v1_account_misc_read_val( - 'price.' + str(items[0]), -1) + 'price.' + str(items[0]), -1 + ) price_str = ba.charstr(ba.SpecialChar.TICKET) + str(price) - self._price_text = ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, - 150 + pyoffs), - size=(0, 0), - text=price_str, - h_align='center', - v_align='center', - maxwidth=self._width * 0.9, - scale=1.4, - color=(0.2, 1, 0.2)) + self._price_text = ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, 150 + pyoffs), + size=(0, 0), + text=price_str, + h_align='center', + v_align='center', + maxwidth=self._width * 0.9, + scale=1.4, + color=(0.2, 1, 0.2), + ) - self._update_timer = ba.Timer(1.0, - ba.WeakCall(self._update), - timetype=ba.TimeType.REAL, - repeat=True) + self._update_timer = ba.Timer( + 1.0, + ba.WeakCall(self._update), + timetype=ba.TimeType.REAL, + repeat=True, + ) self._cancel_button = ba.buttonwidget( parent=self._root_widget, @@ -98,7 +125,8 @@ class PurchaseWindow(ba.Window): scale=1.0, on_activate_call=self._cancel, autoselect=True, - label=ba.Lstr(resource='cancelText')) + label=ba.Lstr(resource='cancelText'), + ) self._purchase_button = ba.buttonwidget( parent=self._root_widget, position=(self._width - 200, 40), @@ -106,12 +134,15 @@ class PurchaseWindow(ba.Window): scale=1.0, on_activate_call=self._purchase, autoselect=True, - label=ba.Lstr(resource='store.purchaseText')) + label=ba.Lstr(resource='store.purchaseText'), + ) - ba.containerwidget(edit=self._root_widget, - cancel_button=self._cancel_button, - start_button=self._purchase_button, - selected_child=self._purchase_button) + ba.containerwidget( + edit=self._root_widget, + cancel_button=self._cancel_button, + start_button=self._purchase_button, + selected_child=self._purchase_button, + ) def _update(self) -> None: can_die = False @@ -129,6 +160,7 @@ class PurchaseWindow(ba.Window): def _purchase(self) -> None: from bastd.ui import getcurrency + if self._items == ['pro']: ba.internal.purchase('pro') else: diff --git a/dist/ba_data/python/bastd/ui/qrcode.py b/dist/ba_data/python/bastd/ui/qrcode.py index 16efd5d..76e5de2 100644 --- a/dist/ba_data/python/bastd/ui/qrcode.py +++ b/dist/ba_data/python/bastd/ui/qrcode.py @@ -14,17 +14,24 @@ class QRCodeWindow(popup.PopupWindow): position = origin_widget.get_screen_space_center() uiscale = ba.app.ui.uiscale - scale = (2.3 if uiscale is ba.UIScale.SMALL else - 1.65 if uiscale is ba.UIScale.MEDIUM else 1.23) + scale = ( + 2.3 + if uiscale is ba.UIScale.SMALL + else 1.65 + if uiscale is ba.UIScale.MEDIUM + else 1.23 + ) self._transitioning_out = False self._width = 450 self._height = 400 bg_color = (0.5, 0.4, 0.6) - popup.PopupWindow.__init__(self, - position=position, - size=(self._width, self._height), - scale=scale, - bg_color=bg_color) + popup.PopupWindow.__init__( + self, + position=position, + size=(self._width, self._height), + scale=scale, + bg_color=bg_color, + ) self._cancel_button = ba.buttonwidget( parent=self.root_widget, position=(50, self._height - 30), @@ -35,12 +42,14 @@ class QRCodeWindow(popup.PopupWindow): on_activate_call=self._on_cancel_press, autoselect=True, icon=ba.gettexture('crossOut'), - iconscale=1.2) - ba.imagewidget(parent=self.root_widget, - position=(self._width * 0.5 - 150, - self._height * 0.5 - 150), - size=(300, 300), - texture=qr_tex) + iconscale=1.2, + ) + ba.imagewidget( + parent=self.root_widget, + position=(self._width * 0.5 - 150, self._height * 0.5 - 150), + size=(300, 300), + texture=qr_tex, + ) def _on_cancel_press(self) -> None: self._transition_out() diff --git a/dist/ba_data/python/bastd/ui/radiogroup.py b/dist/ba_data/python/bastd/ui/radiogroup.py index 39f2aff..1cc8301 100644 --- a/dist/ba_data/python/bastd/ui/radiogroup.py +++ b/dist/ba_data/python/bastd/ui/radiogroup.py @@ -12,22 +12,30 @@ if TYPE_CHECKING: from typing import Any, Callable, Sequence -def make_radio_group(check_boxes: Sequence[ba.Widget], - value_names: Sequence[str], value: str, - value_change_call: Callable[[str], Any]) -> None: +def make_radio_group( + check_boxes: Sequence[ba.Widget], + value_names: Sequence[str], + value: str, + value_change_call: Callable[[str], Any], +) -> None: """Link the provided check_boxes together into a radio group.""" - def _radio_press(check_string: str, other_check_boxes: list[ba.Widget], - val: int) -> None: + def _radio_press( + check_string: str, other_check_boxes: list[ba.Widget], val: int + ) -> None: if val == 1: value_change_call(check_string) for cbx in other_check_boxes: ba.checkboxwidget(edit=cbx, value=False) for i, check_box in enumerate(check_boxes): - ba.checkboxwidget(edit=check_box, - value=(value == value_names[i]), - is_radio_button=True, - on_value_change_call=ba.Call( - _radio_press, value_names[i], - [c for c in check_boxes if c != check_box])) + ba.checkboxwidget( + edit=check_box, + value=(value == value_names[i]), + is_radio_button=True, + on_value_change_call=ba.Call( + _radio_press, + value_names[i], + [c for c in check_boxes if c != check_box], + ), + ) diff --git a/dist/ba_data/python/bastd/ui/report.py b/dist/ba_data/python/bastd/ui/report.py index 1f322ca..1a76043 100644 --- a/dist/ba_data/python/bastd/ui/report.py +++ b/dist/ba_data/python/bastd/ui/report.py @@ -20,71 +20,102 @@ class ReportPlayerWindow(ba.Window): overlay_stack = ba.internal.get_special_widget('overlay_stack') uiscale = ba.app.ui.uiscale - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height), - parent=overlay_stack, - transition='in_scale', - scale_origin_stack_offset=scale_origin, - scale=(1.8 if uiscale is ba.UIScale.SMALL else - 1.35 if uiscale is ba.UIScale.MEDIUM else 1.0))) - self._cancel_button = ba.buttonwidget(parent=self._root_widget, - scale=0.7, - position=(40, self._height - 50), - size=(50, 50), - label='', - on_activate_call=self.close, - autoselect=True, - color=(0.4, 0.4, 0.5), - icon=ba.gettexture('crossOut'), - iconscale=1.2) - ba.containerwidget(edit=self._root_widget, - cancel_button=self._cancel_button) - ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, self._height * 0.64), - size=(0, 0), - color=(1, 1, 1, 0.8), - scale=1.2, - h_align='center', - v_align='center', - text=ba.Lstr(resource='reportThisPlayerReasonText'), - maxwidth=self._width * 0.85) - ba.buttonwidget(parent=self._root_widget, - size=(235, 60), - position=(20, 30), - label=ba.Lstr(resource='reportThisPlayerLanguageText'), - on_activate_call=self._on_language_press, - autoselect=True) - ba.buttonwidget(parent=self._root_widget, - size=(235, 60), - position=(self._width - 255, 30), - label=ba.Lstr(resource='reportThisPlayerCheatingText'), - on_activate_call=self._on_cheating_press, - autoselect=True) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height), + parent=overlay_stack, + transition='in_scale', + scale_origin_stack_offset=scale_origin, + scale=( + 1.8 + if uiscale is ba.UIScale.SMALL + else 1.35 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + ) + ) + self._cancel_button = ba.buttonwidget( + parent=self._root_widget, + scale=0.7, + position=(40, self._height - 50), + size=(50, 50), + label='', + on_activate_call=self.close, + autoselect=True, + color=(0.4, 0.4, 0.5), + icon=ba.gettexture('crossOut'), + iconscale=1.2, + ) + ba.containerwidget( + edit=self._root_widget, cancel_button=self._cancel_button + ) + ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, self._height * 0.64), + size=(0, 0), + color=(1, 1, 1, 0.8), + scale=1.2, + h_align='center', + v_align='center', + text=ba.Lstr(resource='reportThisPlayerReasonText'), + maxwidth=self._width * 0.85, + ) + ba.buttonwidget( + parent=self._root_widget, + size=(235, 60), + position=(20, 30), + label=ba.Lstr(resource='reportThisPlayerLanguageText'), + on_activate_call=self._on_language_press, + autoselect=True, + ) + ba.buttonwidget( + parent=self._root_widget, + size=(235, 60), + position=(self._width - 255, 30), + label=ba.Lstr(resource='reportThisPlayerCheatingText'), + on_activate_call=self._on_cheating_press, + autoselect=True, + ) def _on_language_press(self) -> None: from urllib import parse - ba.internal.add_transaction({ - 'type': 'REPORT_ACCOUNT', - 'reason': 'language', - 'account': self._account_id - }) + + ba.internal.add_transaction( + { + 'type': 'REPORT_ACCOUNT', + 'reason': 'language', + 'account': self._account_id, + } + ) body = ba.Lstr(resource='reportPlayerExplanationText').evaluate() - ba.open_url('mailto:support@froemling.net' - f'?subject={ba.internal.appnameupper()} Player Report: ' + - self._account_id + '&body=' + parse.quote(body)) + ba.open_url( + 'mailto:support@froemling.net' + f'?subject={ba.internal.appnameupper()} Player Report: ' + + self._account_id + + '&body=' + + parse.quote(body) + ) self.close() def _on_cheating_press(self) -> None: from urllib import parse - ba.internal.add_transaction({ - 'type': 'REPORT_ACCOUNT', - 'reason': 'cheating', - 'account': self._account_id - }) + + ba.internal.add_transaction( + { + 'type': 'REPORT_ACCOUNT', + 'reason': 'cheating', + 'account': self._account_id, + } + ) body = ba.Lstr(resource='reportPlayerExplanationText').evaluate() - ba.open_url('mailto:support@froemling.net' - f'?subject={ba.internal.appnameupper()} Player Report: ' + - self._account_id + '&body=' + parse.quote(body)) + ba.open_url( + 'mailto:support@froemling.net' + f'?subject={ba.internal.appnameupper()} Player Report: ' + + self._account_id + + '&body=' + + parse.quote(body) + ) self.close() def close(self) -> None: diff --git a/dist/ba_data/python/bastd/ui/resourcetypeinfo.py b/dist/ba_data/python/bastd/ui/resourcetypeinfo.py index 2013f5c..2bd829b 100644 --- a/dist/ba_data/python/bastd/ui/resourcetypeinfo.py +++ b/dist/ba_data/python/bastd/ui/resourcetypeinfo.py @@ -13,8 +13,13 @@ class ResourceTypeInfoWindow(popup.PopupWindow): def __init__(self, origin_widget: ba.Widget): uiscale = ba.app.ui.uiscale - scale = (2.3 if uiscale is ba.UIScale.SMALL else - 1.65 if uiscale is ba.UIScale.MEDIUM else 1.23) + scale = ( + 2.3 + if uiscale is ba.UIScale.SMALL + else 1.65 + if uiscale is ba.UIScale.MEDIUM + else 1.23 + ) self._transitioning_out = False self._width = 570 self._height = 350 @@ -25,7 +30,8 @@ class ResourceTypeInfoWindow(popup.PopupWindow): toolbar_visibility='inherit', scale=scale, bg_color=bg_color, - position=origin_widget.get_screen_space_center()) + position=origin_widget.get_screen_space_center(), + ) self._cancel_button = ba.buttonwidget( parent=self.root_widget, position=(50, self._height - 30), @@ -36,7 +42,8 @@ class ResourceTypeInfoWindow(popup.PopupWindow): on_activate_call=self._on_cancel_press, autoselect=True, icon=ba.gettexture('crossOut'), - iconscale=1.2) + iconscale=1.2, + ) def _on_cancel_press(self) -> None: self._transition_out() diff --git a/dist/ba_data/python/bastd/ui/serverdialog.py b/dist/ba_data/python/bastd/ui/serverdialog.py index 8609982..6f3ec3c 100644 --- a/dist/ba_data/python/bastd/ui/serverdialog.py +++ b/dist/ba_data/python/bastd/ui/serverdialog.py @@ -21,10 +21,12 @@ if TYPE_CHECKING: @dataclass class ServerDialogData: """Data for ServerDialog.""" + dialog_id: Annotated[str, IOAttrs('dialogID')] text: Annotated[str, IOAttrs('text')] - subs: Annotated[list[tuple[str, str]], - IOAttrs('subs')] = field(default_factory=list) + subs: Annotated[list[tuple[str, str]], IOAttrs('subs')] = field( + default_factory=list + ) show_cancel: Annotated[bool, IOAttrs('showCancel')] = True copy_text: Annotated[str | None, IOAttrs('copyText')] = None @@ -34,35 +36,46 @@ class ServerDialogWindow(ba.Window): def __init__(self, data: ServerDialogData): self._data = data - txt = ba.Lstr(translate=('serverResponses', data.text), - subs=data.subs).evaluate() + txt = ba.Lstr( + translate=('serverResponses', data.text), subs=data.subs + ).evaluate() txt = txt.strip() txt_scale = 1.5 txt_height = ( - ba.internal.get_string_height(txt, suppress_warning=True) * - txt_scale) + ba.internal.get_string_height(txt, suppress_warning=True) + * txt_scale + ) self._width = 500 self._height = 160 + min(200, txt_height) uiscale = ba.app.ui.uiscale - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height), - transition='in_scale', - scale=(1.8 if uiscale is ba.UIScale.SMALL else - 1.35 if uiscale is ba.UIScale.MEDIUM else 1.0))) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height), + transition='in_scale', + scale=( + 1.8 + if uiscale is ba.UIScale.SMALL + else 1.35 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + ) + ) self._starttime = ba.time(ba.TimeType.REAL) ba.playsound(ba.getsound('swish')) - ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, - 70 + (self._height - 70) * 0.5), - size=(0, 0), - color=(1.0, 3.0, 1.0), - scale=txt_scale, - h_align='center', - v_align='center', - text=txt, - maxwidth=self._width * 0.85, - max_height=(self._height - 110)) + ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, 70 + (self._height - 70) * 0.5), + size=(0, 0), + color=(1.0, 3.0, 1.0), + scale=txt_scale, + h_align='center', + v_align='center', + text=txt, + maxwidth=self._width * 0.85, + max_height=(self._height - 110), + ) show_copy = data.copy_text is not None and ba.clipboard_is_supported() @@ -70,41 +83,58 @@ class ServerDialogWindow(ba.Window): # spot. Cancel wins in this case since it is important functionality # and copy is just for convenience (and not even always available). if show_copy and data.show_cancel: - logging.warning('serverdialog requesting both copy and cancel;' - ' copy will not be shown.') + logging.warning( + 'serverdialog requesting both copy and cancel;' + ' copy will not be shown.' + ) show_copy = False - self._cancel_button = (None - if not data.show_cancel else ba.buttonwidget( - parent=self._root_widget, - position=(30, 30), - size=(160, 60), - autoselect=True, - label=ba.Lstr(resource='cancelText'), - on_activate_call=self._cancel_press)) + self._cancel_button = ( + None + if not data.show_cancel + else ba.buttonwidget( + parent=self._root_widget, + position=(30, 30), + size=(160, 60), + autoselect=True, + label=ba.Lstr(resource='cancelText'), + on_activate_call=self._cancel_press, + ) + ) - self._copy_button = None if not show_copy else ba.buttonwidget( - parent=self._root_widget, - position=(30, 30), - size=(160, 60), - autoselect=True, - label=ba.Lstr(resource='copyText'), - on_activate_call=self._copy_press) + self._copy_button = ( + None + if not show_copy + else ba.buttonwidget( + parent=self._root_widget, + position=(30, 30), + size=(160, 60), + autoselect=True, + label=ba.Lstr(resource='copyText'), + on_activate_call=self._copy_press, + ) + ) self._ok_button = ba.buttonwidget( parent=self._root_widget, - position=((self._width - 182) if - (data.show_cancel or show_copy) else - (self._width * 0.5 - 80), 30), + position=( + (self._width - 182) + if (data.show_cancel or show_copy) + else (self._width * 0.5 - 80), + 30, + ), size=(160, 60), autoselect=True, label=ba.Lstr(resource='okText'), - on_activate_call=self._ok_press) + on_activate_call=self._ok_press, + ) - ba.containerwidget(edit=self._root_widget, - cancel_button=self._cancel_button, - start_button=self._ok_button, - selected_child=self._ok_button) + ba.containerwidget( + edit=self._root_widget, + cancel_button=self._cancel_button, + start_button=self._ok_button, + selected_child=self._ok_button, + ) def _copy_press(self) -> None: assert self._data.copy_text is not None @@ -115,20 +145,24 @@ class ServerDialogWindow(ba.Window): if ba.time(ba.TimeType.REAL) - self._starttime < 1.0: ba.playsound(ba.getsound('error')) return - ba.internal.add_transaction({ - 'type': 'DIALOG_RESPONSE', - 'dialogID': self._data.dialog_id, - 'response': 1 - }) + ba.internal.add_transaction( + { + 'type': 'DIALOG_RESPONSE', + 'dialogID': self._data.dialog_id, + 'response': 1, + } + ) ba.containerwidget(edit=self._root_widget, transition='out_scale') def _cancel_press(self) -> None: if ba.time(ba.TimeType.REAL) - self._starttime < 1.0: ba.playsound(ba.getsound('error')) return - ba.internal.add_transaction({ - 'type': 'DIALOG_RESPONSE', - 'dialogID': self._data.dialog_id, - 'response': 0 - }) + ba.internal.add_transaction( + { + 'type': 'DIALOG_RESPONSE', + 'dialogID': self._data.dialog_id, + 'response': 0, + } + ) ba.containerwidget(edit=self._root_widget, transition='out_scale') diff --git a/dist/ba_data/python/bastd/ui/settings/advanced.py b/dist/ba_data/python/bastd/ui/settings/advanced.py index 8a85f12..f088134 100644 --- a/dist/ba_data/python/bastd/ui/settings/advanced.py +++ b/dist/ba_data/python/bastd/ui/settings/advanced.py @@ -17,9 +17,11 @@ if TYPE_CHECKING: class AdvancedSettingsWindow(ba.Window): """Window for editing advanced game settings.""" - def __init__(self, - transition: str = 'in_right', - origin_widget: ba.Widget | None = None): + def __init__( + self, + transition: str = 'in_right', + origin_widget: ba.Widget | None = None, + ): # pylint: disable=too-many-statements from ba.internal import master_server_get import threading @@ -43,19 +45,34 @@ class AdvancedSettingsWindow(ba.Window): uiscale = ba.app.ui.uiscale self._width = 870.0 if uiscale is ba.UIScale.SMALL else 670.0 x_inset = 100 if uiscale is ba.UIScale.SMALL else 0 - self._height = (390.0 if uiscale is ba.UIScale.SMALL else - 450.0 if uiscale is ba.UIScale.MEDIUM else 520.0) + self._height = ( + 390.0 + if uiscale is ba.UIScale.SMALL + else 450.0 + if uiscale is ba.UIScale.MEDIUM + else 520.0 + ) self._spacing = 32 self._menu_open = False top_extra = 10 if uiscale is ba.UIScale.SMALL else 0 - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height + top_extra), - transition=transition, - toolbar_visibility='menu_minimal', - scale_origin_stack_offset=scale_origin, - scale=(2.06 if uiscale is ba.UIScale.SMALL else - 1.4 if uiscale is ba.UIScale.MEDIUM else 1.0), - stack_offset=(0, -25) if uiscale is ba.UIScale.SMALL else (0, 0))) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height + top_extra), + transition=transition, + toolbar_visibility='menu_minimal', + scale_origin_stack_offset=scale_origin, + scale=( + 2.06 + if uiscale is ba.UIScale.SMALL + else 1.4 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + stack_offset=(0, -25) + if uiscale is ba.UIScale.SMALL + else (0, 0), + ) + ) self._prev_lang = '' self._prev_lang_list: list[str] = [] @@ -65,8 +82,9 @@ class AdvancedSettingsWindow(ba.Window): # In vr-mode, the internal keyboard is currently the *only* option, # so no need to show this. - self._show_always_use_internal_keyboard = (not app.vr_mode - and not app.iircade_mode) + self._show_always_use_internal_keyboard = ( + not app.vr_mode and not app.iircade_mode + ) self._scroll_width = self._width - (100 + 2 * x_inset) self._scroll_height = self._height - 115.0 @@ -93,8 +111,9 @@ class AdvancedSettingsWindow(ba.Window): self._r = 'settingsWindowAdvanced' if app.ui.use_toolbars and uiscale is ba.UIScale.SMALL: - ba.containerwidget(edit=self._root_widget, - on_cancel_call=self._do_back) + ba.containerwidget( + edit=self._root_widget, on_cancel_call=self._do_back + ) self._back_button = None else: self._back_button = ba.buttonwidget( @@ -105,50 +124,62 @@ class AdvancedSettingsWindow(ba.Window): autoselect=True, label=ba.Lstr(resource='backText'), button_type='back', - on_activate_call=self._do_back) - ba.containerwidget(edit=self._root_widget, - cancel_button=self._back_button) + on_activate_call=self._do_back, + ) + ba.containerwidget( + edit=self._root_widget, cancel_button=self._back_button + ) - self._title_text = ba.textwidget(parent=self._root_widget, - position=(0, self._height - 52), - size=(self._width, 25), - text=ba.Lstr(resource=self._r + - '.titleText'), - color=app.ui.title_color, - h_align='center', - v_align='top') + self._title_text = ba.textwidget( + parent=self._root_widget, + position=(0, self._height - 52), + size=(self._width, 25), + text=ba.Lstr(resource=self._r + '.titleText'), + color=app.ui.title_color, + h_align='center', + v_align='top', + ) if self._back_button is not None: - ba.buttonwidget(edit=self._back_button, - button_type='backSmall', - size=(60, 60), - label=ba.charstr(ba.SpecialChar.BACK)) + ba.buttonwidget( + edit=self._back_button, + button_type='backSmall', + size=(60, 60), + label=ba.charstr(ba.SpecialChar.BACK), + ) - self._scrollwidget = ba.scrollwidget(parent=self._root_widget, - position=(50 + x_inset, 50), - simple_culling_v=20.0, - highlight=False, - size=(self._scroll_width, - self._scroll_height), - selection_loops_to_parent=True) + self._scrollwidget = ba.scrollwidget( + parent=self._root_widget, + position=(50 + x_inset, 50), + simple_culling_v=20.0, + highlight=False, + size=(self._scroll_width, self._scroll_height), + selection_loops_to_parent=True, + ) ba.widget(edit=self._scrollwidget, right_widget=self._scrollwidget) - self._subcontainer = ba.containerwidget(parent=self._scrollwidget, - size=(self._sub_width, - self._sub_height), - background=False, - selection_loops_to_parent=True) + self._subcontainer = ba.containerwidget( + parent=self._scrollwidget, + size=(self._sub_width, self._sub_height), + background=False, + selection_loops_to_parent=True, + ) self._rebuild() # Rebuild periodically to pick up language changes/additions/etc. - self._rebuild_timer = ba.Timer(1.0, - ba.WeakCall(self._rebuild), - repeat=True, - timetype=ba.TimeType.REAL) + self._rebuild_timer = ba.Timer( + 1.0, + ba.WeakCall(self._rebuild), + repeat=True, + timetype=ba.TimeType.REAL, + ) # Fetch the list of completed languages. - master_server_get('bsLangGetCompleted', {'b': app.build_number}, - callback=ba.WeakCall(self._completed_langs_cb)) + master_server_get( + 'bsLangGetCompleted', + {'b': app.build_number}, + callback=ba.WeakCall(self._completed_langs_cb), + ) # noinspection PyUnresolvedReferences @staticmethod @@ -166,23 +197,32 @@ class AdvancedSettingsWindow(ba.Window): def _update_lang_status(self) -> None: if self._complete_langs_list is not None: - up_to_date = (ba.app.lang.language in self._complete_langs_list) + up_to_date = ba.app.lang.language in self._complete_langs_list ba.textwidget( edit=self._lang_status_text, - text='' if ba.app.lang.language == 'Test' else ba.Lstr( - resource=self._r + '.translationNoUpdateNeededText') - if up_to_date else ba.Lstr(resource=self._r + - '.translationUpdateNeededText'), - color=(0.2, 1.0, 0.2, 0.8) if up_to_date else - (1.0, 0.2, 0.2, 0.8)) + text='' + if ba.app.lang.language == 'Test' + else ba.Lstr( + resource=self._r + '.translationNoUpdateNeededText' + ) + if up_to_date + else ba.Lstr(resource=self._r + '.translationUpdateNeededText'), + color=(0.2, 1.0, 0.2, 0.8) + if up_to_date + else (1.0, 0.2, 0.2, 0.8), + ) else: ba.textwidget( edit=self._lang_status_text, text=ba.Lstr(resource=self._r + '.translationFetchErrorText') - if self._complete_langs_error else ba.Lstr( - resource=self._r + '.translationFetchingStatusText'), - color=(1.0, 0.5, 0.2) if self._complete_langs_error else - (0.7, 0.7, 0.7)) + if self._complete_langs_error + else ba.Lstr( + resource=self._r + '.translationFetchingStatusText' + ), + color=(1.0, 0.5, 0.2) + if self._complete_langs_error + else (0.7, 0.7, 0.7), + ) def _rebuild(self) -> None: # pylint: disable=too-many-statements @@ -200,8 +240,10 @@ class AdvancedSettingsWindow(ba.Window): # menu based on the language so still need this. ...however we could # make this more limited to it only rebuilds that one menu instead # of everything. - if self._menu_open or (self._prev_lang == ba.app.config.get( - 'Lang', None) and self._prev_lang_list == available_languages): + if self._menu_open or ( + self._prev_lang == ba.app.config.get('Lang', None) + and self._prev_lang_list == available_languages + ): return self._prev_lang = ba.app.config.get('Lang', None) self._prev_lang_list = available_languages @@ -217,13 +259,16 @@ class AdvancedSettingsWindow(ba.Window): # Update our existing back button and title. if self._back_button is not None: - ba.buttonwidget(edit=self._back_button, - label=ba.Lstr(resource='backText')) - ba.buttonwidget(edit=self._back_button, - label=ba.charstr(ba.SpecialChar.BACK)) + ba.buttonwidget( + edit=self._back_button, label=ba.Lstr(resource='backText') + ) + ba.buttonwidget( + edit=self._back_button, label=ba.charstr(ba.SpecialChar.BACK) + ) - ba.textwidget(edit=self._title_text, - text=ba.Lstr(resource=self._r + '.titleText')) + ba.textwidget( + edit=self._title_text, text=ba.Lstr(resource=self._r + '.titleText') + ) this_button_width = 410 @@ -234,22 +279,27 @@ class AdvancedSettingsWindow(ba.Window): autoselect=True, label=ba.Lstr(resource=self._r + '.enterPromoCodeText'), text_scale=1.0, - on_activate_call=self._on_promo_code_press) + on_activate_call=self._on_promo_code_press, + ) if self._back_button is not None: - ba.widget(edit=self._promo_code_button, - up_widget=self._back_button, - left_widget=self._back_button) + ba.widget( + edit=self._promo_code_button, + up_widget=self._back_button, + left_widget=self._back_button, + ) v -= self._extra_button_spacing * 0.8 - ba.textwidget(parent=self._subcontainer, - position=(200, v + 10), - size=(0, 0), - text=ba.Lstr(resource=self._r + '.languageText'), - maxwidth=150, - scale=0.95, - color=ba.app.ui.title_color, - h_align='right', - v_align='center') + ba.textwidget( + parent=self._subcontainer, + position=(200, v + 10), + size=(0, 0), + text=ba.Lstr(resource=self._r + '.languageText'), + maxwidth=150, + scale=0.95, + color=ba.app.ui.title_color, + h_align='right', + v_align='center', + ) languages = ba.app.lang.available_languages cur_lang = ba.app.config.get('Lang', None) @@ -260,10 +310,11 @@ class AdvancedSettingsWindow(ba.Window): # so we don't have to go digging through each full language. try: import json - with open('ba_data/data/langdata.json', - encoding='utf-8') as infile: - lang_names_translated = (json.loads( - infile.read())['lang_names_translated']) + + with open('ba_data/data/langdata.json', encoding='utf-8') as infile: + lang_names_translated = json.loads(infile.read())[ + 'lang_names_translated' + ] except Exception: ba.print_exception('Error reading lang data.') lang_names_translated = {} @@ -278,8 +329,9 @@ class AdvancedSettingsWindow(ba.Window): if langs_translated[lang] == lang_translated: langs_full[lang] = lang_translated else: - langs_full[lang] = (langs_translated[lang] + ' (' + - lang_translated + ')') + langs_full[lang] = ( + langs_translated[lang] + ' (' + lang_translated + ')' + ) self._language_popup = popup_ui.PopupMenu( parent=self._subcontainer, @@ -291,52 +343,72 @@ class AdvancedSettingsWindow(ba.Window): on_value_change_call=ba.WeakCall(self._on_menu_choice), choices=['Auto'] + languages, button_size=(250, 60), - choices_display=([ - ba.Lstr(value=(ba.Lstr(resource='autoText').evaluate() + ' (' + - ba.Lstr(translate=('languages', - ba.app.lang.default_language - )).evaluate() + ')')) - ] + [ba.Lstr(value=langs_full[l]) for l in languages]), - current_choice=cur_lang) + choices_display=( + [ + ba.Lstr( + value=( + ba.Lstr(resource='autoText').evaluate() + + ' (' + + ba.Lstr( + translate=( + 'languages', + ba.app.lang.default_language, + ) + ).evaluate() + + ')' + ) + ) + ] + + [ba.Lstr(value=langs_full[l]) for l in languages] + ), + current_choice=cur_lang, + ) v -= self._spacing * 1.8 - ba.textwidget(parent=self._subcontainer, - position=(self._sub_width * 0.5, v + 10), - size=(0, 0), - text=ba.Lstr(resource=self._r + '.helpTranslateText', - subs=[('${APP_NAME}', - ba.Lstr(resource='titleText'))]), - maxwidth=self._sub_width * 0.9, - max_height=55, - flatness=1.0, - scale=0.65, - color=(0.4, 0.9, 0.4, 0.8), - h_align='center', - v_align='center') + ba.textwidget( + parent=self._subcontainer, + position=(self._sub_width * 0.5, v + 10), + size=(0, 0), + text=ba.Lstr( + resource=self._r + '.helpTranslateText', + subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))], + ), + maxwidth=self._sub_width * 0.9, + max_height=55, + flatness=1.0, + scale=0.65, + color=(0.4, 0.9, 0.4, 0.8), + h_align='center', + v_align='center', + ) v -= self._spacing * 1.9 this_button_width = 410 self._translation_editor_button = ba.buttonwidget( parent=self._subcontainer, position=(self._sub_width / 2 - this_button_width / 2, v - 24), size=(this_button_width, 60), - label=ba.Lstr(resource=self._r + '.translationEditorButtonText', - subs=[('${APP_NAME}', ba.Lstr(resource='titleText')) - ]), + label=ba.Lstr( + resource=self._r + '.translationEditorButtonText', + subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))], + ), autoselect=True, on_activate_call=ba.Call( - ba.open_url, 'https://legacy.ballistica.net/translate')) + ba.open_url, 'https://legacy.ballistica.net/translate' + ), + ) - self._lang_status_text = ba.textwidget(parent=self._subcontainer, - position=(self._sub_width * 0.5, - v - 40), - size=(0, 0), - text='', - flatness=1.0, - scale=0.63, - h_align='center', - v_align='center', - maxwidth=400.0) + self._lang_status_text = ba.textwidget( + parent=self._subcontainer, + position=(self._sub_width * 0.5, v - 40), + size=(0, 0), + text='', + flatness=1.0, + scale=0.63, + h_align='center', + v_align='center', + maxwidth=400.0, + ) self._update_lang_status() v -= 40 @@ -351,12 +423,14 @@ class AdvancedSettingsWindow(ba.Window): textcolor=(0.8, 0.8, 0.8), value=lang_inform, text=ba.Lstr(resource=self._r + '.translationInformMe'), - on_value_change_call=ba.WeakCall( - self._on_lang_inform_value_change)) + on_value_change_call=ba.WeakCall(self._on_lang_inform_value_change), + ) - ba.widget(edit=self._translation_editor_button, - down_widget=cbw, - up_widget=self._language_popup.get_button()) + ba.widget( + edit=self._translation_editor_button, + down_widget=cbw, + up_widget=self._language_popup.get_button(), + ) v -= self._spacing * 3.0 @@ -367,7 +441,19 @@ class AdvancedSettingsWindow(ba.Window): configkey='Kick Idle Players', displayname=ba.Lstr(resource=self._r + '.kickIdlePlayersText'), scale=1.0, - maxwidth=430) + maxwidth=430, + ) + + v -= 42 + self._show_game_ping_check_box = ConfigCheckBox( + parent=self._subcontainer, + position=(50, v), + size=(self._sub_width - 100, 30), + configkey='Show Ping', + displayname=ba.Lstr(value='Show InGame Ping'), + scale=1.0, + maxwidth=430, + ) v -= 42 self._disable_camera_shake_check_box = ConfigCheckBox( @@ -377,7 +463,8 @@ class AdvancedSettingsWindow(ba.Window): configkey='Disable Camera Shake', displayname=ba.Lstr(resource=self._r + '.disableCameraShakeText'), scale=1.0, - maxwidth=430) + maxwidth=430, + ) self._disable_gyro_check_box: ConfigCheckBox | None = None if self._show_disable_gyro: @@ -387,10 +474,12 @@ class AdvancedSettingsWindow(ba.Window): position=(50, v), size=(self._sub_width - 100, 30), configkey='Disable Camera Gyro', - displayname=ba.Lstr(resource=self._r + - '.disableCameraGyroscopeMotionText'), + displayname=ba.Lstr( + resource=self._r + '.disableCameraGyroscopeMotionText' + ), scale=1.0, - maxwidth=430) + maxwidth=430, + ) self._always_use_internal_keyboard_check_box: ConfigCheckBox | None if self._show_always_use_internal_keyboard: @@ -401,22 +490,27 @@ class AdvancedSettingsWindow(ba.Window): size=(self._sub_width - 100, 30), configkey='Always Use Internal Keyboard', autoselect=True, - displayname=ba.Lstr(resource=self._r + - '.alwaysUseInternalKeyboardText'), + displayname=ba.Lstr( + resource=self._r + '.alwaysUseInternalKeyboardText' + ), scale=1.0, - maxwidth=430) + maxwidth=430, + ) ba.textwidget( parent=self._subcontainer, position=(90, v - 10), size=(0, 0), - text=ba.Lstr(resource=self._r + - '.alwaysUseInternalKeyboardDescriptionText'), + text=ba.Lstr( + resource=self._r + + '.alwaysUseInternalKeyboardDescriptionText' + ), maxwidth=400, flatness=1.0, scale=0.65, color=(0.4, 0.9, 0.4, 0.8), h_align='left', - v_align='center') + v_align='center', + ) v -= 20 else: self._always_use_internal_keyboard_check_box = None @@ -432,19 +526,28 @@ class AdvancedSettingsWindow(ba.Window): label=ba.Lstr(resource=self._r + '.moddingGuideText'), text_scale=1.0, on_activate_call=ba.Call( - ba.open_url, 'https://ballistica.net/wiki/modding-guide')) + ba.open_url, 'https://ballistica.net/wiki/modding-guide' + ), + ) if self._show_always_use_internal_keyboard: assert self._always_use_internal_keyboard_check_box is not None - ba.widget(edit=self._always_use_internal_keyboard_check_box.widget, - down_widget=self._modding_guide_button) + ba.widget( + edit=self._always_use_internal_keyboard_check_box.widget, + down_widget=self._modding_guide_button, + ) ba.widget( edit=self._modding_guide_button, - up_widget=self._always_use_internal_keyboard_check_box.widget) + up_widget=self._always_use_internal_keyboard_check_box.widget, + ) else: - ba.widget(edit=self._modding_guide_button, - up_widget=self._kick_idle_players_check_box.widget) - ba.widget(edit=self._kick_idle_players_check_box.widget, - down_widget=self._modding_guide_button) + ba.widget( + edit=self._modding_guide_button, + up_widget=self._kick_idle_players_check_box.widget, + ) + ba.widget( + edit=self._kick_idle_players_check_box.widget, + down_widget=self._modding_guide_button, + ) v -= self._spacing * 2.0 @@ -455,7 +558,8 @@ class AdvancedSettingsWindow(ba.Window): autoselect=True, label=ba.Lstr(resource=self._r + '.showUserModsText'), text_scale=1.0, - on_activate_call=show_user_scripts) + on_activate_call=show_user_scripts, + ) v -= self._spacing * 2.0 @@ -466,7 +570,8 @@ class AdvancedSettingsWindow(ba.Window): autoselect=True, label=ba.Lstr(resource='pluginsText'), text_scale=1.0, - on_activate_call=self._on_plugins_button_press) + on_activate_call=self._on_plugins_button_press, + ) v -= self._spacing * 0.6 @@ -480,7 +585,8 @@ class AdvancedSettingsWindow(ba.Window): autoselect=True, label=ba.Lstr(resource=self._r + '.vrTestingText'), text_scale=1.0, - on_activate_call=self._on_vr_test_press) + on_activate_call=self._on_vr_test_press, + ) else: self._vr_test_button = None @@ -494,7 +600,8 @@ class AdvancedSettingsWindow(ba.Window): autoselect=True, label=ba.Lstr(resource=self._r + '.netTestingText'), text_scale=1.0, - on_activate_call=self._on_net_test_press) + on_activate_call=self._on_net_test_press, + ) else: self._net_test_button = None @@ -506,7 +613,8 @@ class AdvancedSettingsWindow(ba.Window): autoselect=True, label=ba.Lstr(resource=self._r + '.benchmarksText'), text_scale=1.0, - on_activate_call=self._on_benchmark_press) + on_activate_call=self._on_benchmark_press, + ) for child in self._subcontainer.get_children(): ba.widget(edit=child, show_buffer_bottom=30, show_buffer_top=20) @@ -517,48 +625,53 @@ class AdvancedSettingsWindow(ba.Window): if self._back_button is None: ba.widget( edit=self._scrollwidget, - left_widget=ba.internal.get_special_widget('back_button')) + left_widget=ba.internal.get_special_widget('back_button'), + ) self._restore_state() def _show_restart_needed(self, value: Any) -> None: del value # Unused. - ba.screenmessage(ba.Lstr(resource=self._r + '.mustRestartText'), - color=(1, 1, 0)) + ba.screenmessage( + ba.Lstr(resource=self._r + '.mustRestartText'), color=(1, 1, 0) + ) def _on_lang_inform_value_change(self, val: bool) -> None: - ba.internal.add_transaction({ - 'type': 'SET_MISC_VAL', - 'name': 'langInform', - 'value': val - }) + ba.internal.add_transaction( + {'type': 'SET_MISC_VAL', 'name': 'langInform', 'value': val} + ) ba.internal.run_transactions() def _on_vr_test_press(self) -> None: from bastd.ui.settings.vrtesting import VRTestingWindow + self._save_state() ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.ui.set_main_menu_window( - VRTestingWindow(transition='in_right').get_root_widget()) + VRTestingWindow(transition='in_right').get_root_widget() + ) def _on_net_test_press(self) -> None: from bastd.ui.settings.nettesting import NetTestingWindow # Net-testing requires a signed in v1 account. if ba.internal.get_v1_account_state() != 'signed_in': - ba.screenmessage(ba.Lstr(resource='notSignedInErrorText'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr(resource='notSignedInErrorText'), color=(1, 0, 0) + ) ba.playsound(ba.getsound('error')) return self._save_state() ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.ui.set_main_menu_window( - NetTestingWindow(transition='in_right').get_root_widget()) + NetTestingWindow(transition='in_right').get_root_widget() + ) def _on_friend_promo_code_press(self) -> None: from bastd.ui import appinvite from bastd.ui import account + if ba.internal.get_v1_account_state() != 'signed_in': account.show_sign_in_prompt() return @@ -566,11 +679,14 @@ class AdvancedSettingsWindow(ba.Window): def _on_plugins_button_press(self) -> None: from bastd.ui.settings.plugins import PluginSettingsWindow + self._save_state() ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.ui.set_main_menu_window( PluginSettingsWindow( - origin_widget=self._plugins_button).get_root_widget()) + origin_widget=self._plugins_button + ).get_root_widget() + ) def _on_promo_code_press(self) -> None: from bastd.ui.promocode import PromoCodeWindow @@ -584,14 +700,18 @@ class AdvancedSettingsWindow(ba.Window): ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.ui.set_main_menu_window( PromoCodeWindow( - origin_widget=self._promo_code_button).get_root_widget()) + origin_widget=self._promo_code_button + ).get_root_widget() + ) def _on_benchmark_press(self) -> None: from bastd.ui.debug import DebugWindow + self._save_state() ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.ui.set_main_menu_window( - DebugWindow(transition='in_right').get_root_widget()) + DebugWindow(transition='in_right').get_root_widget() + ) def _save_state(self) -> None: # pylint: disable=too-many-branches @@ -609,17 +729,25 @@ class AdvancedSettingsWindow(ba.Window): sel_name = 'Benchmarks' elif sel == self._kick_idle_players_check_box.widget: sel_name = 'KickIdlePlayers' + elif sel == self._show_game_ping_check_box.widget: + sel_name = 'ShowPing' elif sel == self._disable_camera_shake_check_box.widget: sel_name = 'DisableCameraShake' - elif (self._always_use_internal_keyboard_check_box is not None - and sel - == self._always_use_internal_keyboard_check_box.widget): + elif ( + self._always_use_internal_keyboard_check_box is not None + and sel + == self._always_use_internal_keyboard_check_box.widget + ): sel_name = 'AlwaysUseInternalKeyboard' - elif (self._disable_gyro_check_box is not None - and sel == self._disable_gyro_check_box.widget): + elif ( + self._disable_gyro_check_box is not None + and sel == self._disable_gyro_check_box.widget + ): sel_name = 'DisableGyro' - elif (self._language_popup is not None - and sel == self._language_popup.get_button()): + elif ( + self._language_popup is not None + and sel == self._language_popup.get_button() + ): sel_name = 'Languages' elif sel == self._translation_editor_button: sel_name = 'TranslationEditor' @@ -644,13 +772,15 @@ class AdvancedSettingsWindow(ba.Window): def _restore_state(self) -> None: # pylint: disable=too-many-branches try: - sel_name = ba.app.ui.window_states.get(type(self), - {}).get('sel_name') + sel_name = ba.app.ui.window_states.get(type(self), {}).get( + 'sel_name' + ) if sel_name == 'Back': sel = self._back_button else: - ba.containerwidget(edit=self._root_widget, - selected_child=self._scrollwidget) + ba.containerwidget( + edit=self._root_widget, selected_child=self._scrollwidget + ) if sel_name == 'VRTest': sel = self._vr_test_button elif sel_name == 'NetTest': @@ -661,17 +791,23 @@ class AdvancedSettingsWindow(ba.Window): sel = self._benchmarks_button elif sel_name == 'KickIdlePlayers': sel = self._kick_idle_players_check_box.widget + elif sel_name == 'ShowPing': + sel = self._show_game_ping_check_box.widget elif sel_name == 'DisableCameraShake': sel = self._disable_camera_shake_check_box.widget - elif (sel_name == 'AlwaysUseInternalKeyboard' - and self._always_use_internal_keyboard_check_box - is not None): + elif ( + sel_name == 'AlwaysUseInternalKeyboard' + and self._always_use_internal_keyboard_check_box is not None + ): sel = self._always_use_internal_keyboard_check_box.widget - elif (sel_name == 'DisableGyro' - and self._disable_gyro_check_box is not None): + elif ( + sel_name == 'DisableGyro' + and self._disable_gyro_check_box is not None + ): sel = self._disable_gyro_check_box.widget - elif (sel_name == 'Languages' - and self._language_popup is not None): + elif ( + sel_name == 'Languages' and self._language_popup is not None + ): sel = self._language_popup.get_button() elif sel_name == 'TranslationEditor': sel = self._translation_editor_button @@ -686,9 +822,11 @@ class AdvancedSettingsWindow(ba.Window): else: sel = None if sel is not None: - ba.containerwidget(edit=self._subcontainer, - selected_child=sel, - visible_child=sel) + ba.containerwidget( + edit=self._subcontainer, + selected_child=sel, + visible_child=sel, + ) except Exception: ba.print_exception(f'Error restoring state for {self.__class__}') @@ -710,14 +848,19 @@ class AdvancedSettingsWindow(ba.Window): else: self._complete_langs_list = None self._complete_langs_error = True - ba.timer(0.001, - ba.WeakCall(self._update_lang_status), - timetype=ba.TimeType.REAL) + ba.timer( + 0.001, + ba.WeakCall(self._update_lang_status), + timetype=ba.TimeType.REAL, + ) def _do_back(self) -> None: from bastd.ui.settings.allsettings import AllSettingsWindow + self._save_state() - ba.containerwidget(edit=self._root_widget, - transition=self._transition_out) + ba.containerwidget( + edit=self._root_widget, transition=self._transition_out + ) ba.app.ui.set_main_menu_window( - AllSettingsWindow(transition='in_left').get_root_widget()) + AllSettingsWindow(transition='in_left').get_root_widget() + ) diff --git a/dist/ba_data/python/bastd/ui/settings/allsettings.py b/dist/ba_data/python/bastd/ui/settings/allsettings.py index b1f08f0..9882a93 100644 --- a/dist/ba_data/python/bastd/ui/settings/allsettings.py +++ b/dist/ba_data/python/bastd/ui/settings/allsettings.py @@ -16,9 +16,11 @@ if TYPE_CHECKING: class AllSettingsWindow(ba.Window): """Window for selecting a settings category.""" - def __init__(self, - transition: str = 'in_right', - origin_widget: ba.Widget | None = None): + def __init__( + self, + transition: str = 'in_right', + origin_widget: ba.Widget | None = None, + ): # pylint: disable=too-many-statements # pylint: disable=too-many-locals import threading @@ -44,19 +46,28 @@ class AllSettingsWindow(ba.Window): top_extra = 20 if uiscale is ba.UIScale.SMALL else 0 uiscale = ba.app.ui.uiscale - super().__init__(root_widget=ba.containerwidget( - size=(width, height + top_extra), - transition=transition, - toolbar_visibility='menu_minimal', - scale_origin_stack_offset=scale_origin, - scale=(1.75 if uiscale is ba.UIScale.SMALL else - 1.35 if uiscale is ba.UIScale.MEDIUM else 1.0), - stack_offset=(0, -8) if uiscale is ba.UIScale.SMALL else (0, 0))) + super().__init__( + root_widget=ba.containerwidget( + size=(width, height + top_extra), + transition=transition, + toolbar_visibility='menu_minimal', + scale_origin_stack_offset=scale_origin, + scale=( + 1.75 + if uiscale is ba.UIScale.SMALL + else 1.35 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + stack_offset=(0, -8) if uiscale is ba.UIScale.SMALL else (0, 0), + ) + ) if ba.app.ui.use_toolbars and uiscale is ba.UIScale.SMALL: self._back_button = None - ba.containerwidget(edit=self._root_widget, - on_cancel_call=self._do_back) + ba.containerwidget( + edit=self._root_widget, on_cancel_call=self._do_back + ) else: self._back_button = btn = ba.buttonwidget( parent=self._root_widget, @@ -67,47 +78,56 @@ class AllSettingsWindow(ba.Window): text_scale=1.2, label=ba.Lstr(resource='backText'), button_type='back', - on_activate_call=self._do_back) + on_activate_call=self._do_back, + ) ba.containerwidget(edit=self._root_widget, cancel_button=btn) - ba.textwidget(parent=self._root_widget, - position=(0, height - 44), - size=(width, 25), - text=ba.Lstr(resource=self._r + '.titleText'), - color=ba.app.ui.title_color, - h_align='center', - v_align='center', - maxwidth=130) + ba.textwidget( + parent=self._root_widget, + position=(0, height - 44), + size=(width, 25), + text=ba.Lstr(resource=self._r + '.titleText'), + color=ba.app.ui.title_color, + h_align='center', + v_align='center', + maxwidth=130, + ) if self._back_button is not None: - ba.buttonwidget(edit=self._back_button, - button_type='backSmall', - size=(60, 60), - label=ba.charstr(ba.SpecialChar.BACK)) + ba.buttonwidget( + edit=self._back_button, + button_type='backSmall', + size=(60, 60), + label=ba.charstr(ba.SpecialChar.BACK), + ) v = height - 80 v -= 145 basew = 280 if uiscale is ba.UIScale.SMALL else 230 baseh = 170 - x_offs = x_inset + (105 if uiscale is ba.UIScale.SMALL else - 72) - basew # now unused + x_offs = ( + x_inset + (105 if uiscale is ba.UIScale.SMALL else 72) - basew + ) # now unused x_offs2 = x_offs + basew - 7 x_offs3 = x_offs + 2 * (basew - 7) x_offs4 = x_offs2 x_offs5 = x_offs3 - def _b_title(x: float, y: float, button: ba.Widget, - text: str | ba.Lstr) -> None: - ba.textwidget(parent=self._root_widget, - text=text, - position=(x + basew * 0.47, y + baseh * 0.22), - maxwidth=basew * 0.7, - size=(0, 0), - h_align='center', - v_align='center', - draw_controller=button, - color=(0.7, 0.9, 0.7, 1.0)) + def _b_title( + x: float, y: float, button: ba.Widget, text: str | ba.Lstr + ) -> None: + ba.textwidget( + parent=self._root_widget, + text=text, + position=(x + basew * 0.47, y + baseh * 0.22), + maxwidth=basew * 0.7, + size=(0, 0), + h_align='center', + v_align='center', + draw_controller=button, + color=(0.7, 0.9, 0.7, 1.0), + ) ctb = self._controllers_button = ba.buttonwidget( parent=self._root_widget, @@ -116,18 +136,22 @@ class AllSettingsWindow(ba.Window): size=(basew, baseh), button_type='square', label='', - on_activate_call=self._do_controllers) + on_activate_call=self._do_controllers, + ) if ba.app.ui.use_toolbars and self._back_button is None: bbtn = ba.internal.get_special_widget('back_button') ba.widget(edit=ctb, left_widget=bbtn) - _b_title(x_offs2, v, ctb, - ba.Lstr(resource=self._r + '.controllersText')) + _b_title( + x_offs2, v, ctb, ba.Lstr(resource=self._r + '.controllersText') + ) imgw = imgh = 130 - ba.imagewidget(parent=self._root_widget, - position=(x_offs2 + basew * 0.49 - imgw * 0.5, v + 35), - size=(imgw, imgh), - texture=ba.gettexture('controllerIcon'), - draw_controller=ctb) + ba.imagewidget( + parent=self._root_widget, + position=(x_offs2 + basew * 0.49 - imgw * 0.5, v + 35), + size=(imgw, imgh), + texture=ba.gettexture('controllerIcon'), + draw_controller=ctb, + ) gfxb = self._graphics_button = ba.buttonwidget( parent=self._root_widget, @@ -136,19 +160,22 @@ class AllSettingsWindow(ba.Window): size=(basew, baseh), button_type='square', label='', - on_activate_call=self._do_graphics) + on_activate_call=self._do_graphics, + ) if ba.app.ui.use_toolbars: pbtn = ba.internal.get_special_widget('party_button') ba.widget(edit=gfxb, up_widget=pbtn, right_widget=pbtn) _b_title(x_offs3, v, gfxb, ba.Lstr(resource=self._r + '.graphicsText')) imgw = imgh = 110 - ba.imagewidget(parent=self._root_widget, - position=(x_offs3 + basew * 0.49 - imgw * 0.5, v + 42), - size=(imgw, imgh), - texture=ba.gettexture('graphicsIcon'), - draw_controller=gfxb) + ba.imagewidget( + parent=self._root_widget, + position=(x_offs3 + basew * 0.49 - imgw * 0.5, v + 42), + size=(imgw, imgh), + texture=ba.gettexture('graphicsIcon'), + draw_controller=gfxb, + ) - v -= (baseh - 5) + v -= baseh - 5 abtn = self._audio_button = ba.buttonwidget( parent=self._root_widget, @@ -157,16 +184,18 @@ class AllSettingsWindow(ba.Window): size=(basew, baseh), button_type='square', label='', - on_activate_call=self._do_audio) + on_activate_call=self._do_audio, + ) _b_title(x_offs4, v, abtn, ba.Lstr(resource=self._r + '.audioText')) imgw = imgh = 120 - ba.imagewidget(parent=self._root_widget, - position=(x_offs4 + basew * 0.49 - imgw * 0.5 + 5, - v + 35), - size=(imgw, imgh), - color=(1, 1, 0), - texture=ba.gettexture('audioIcon'), - draw_controller=abtn) + ba.imagewidget( + parent=self._root_widget, + position=(x_offs4 + basew * 0.49 - imgw * 0.5 + 5, v + 35), + size=(imgw, imgh), + color=(1, 1, 0), + texture=ba.gettexture('audioIcon'), + draw_controller=abtn, + ) avb = self._advanced_button = ba.buttonwidget( parent=self._root_widget, @@ -175,16 +204,18 @@ class AllSettingsWindow(ba.Window): size=(basew, baseh), button_type='square', label='', - on_activate_call=self._do_advanced) + on_activate_call=self._do_advanced, + ) _b_title(x_offs5, v, avb, ba.Lstr(resource=self._r + '.advancedText')) imgw = imgh = 120 - ba.imagewidget(parent=self._root_widget, - position=(x_offs5 + basew * 0.49 - imgw * 0.5 + 5, - v + 35), - size=(imgw, imgh), - color=(0.8, 0.95, 1), - texture=ba.gettexture('advancedIcon'), - draw_controller=avb) + ba.imagewidget( + parent=self._root_widget, + position=(x_offs5 + basew * 0.49 - imgw * 0.5 + 5, v + 35), + size=(imgw, imgh), + color=(0.8, 0.95, 1), + texture=ba.gettexture('advancedIcon'), + draw_controller=avb, + ) self._restore_state() # noinspection PyUnresolvedReferences @@ -200,47 +231,62 @@ class AllSettingsWindow(ba.Window): def _do_back(self) -> None: # pylint: disable=cyclic-import from bastd.ui.mainmenu import MainMenuWindow + self._save_state() - ba.containerwidget(edit=self._root_widget, - transition=self._transition_out) + ba.containerwidget( + edit=self._root_widget, transition=self._transition_out + ) ba.app.ui.set_main_menu_window( - MainMenuWindow(transition='in_left').get_root_widget()) + MainMenuWindow(transition='in_left').get_root_widget() + ) def _do_controllers(self) -> None: # pylint: disable=cyclic-import from bastd.ui.settings.controls import ControlsSettingsWindow + self._save_state() ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.ui.set_main_menu_window( ControlsSettingsWindow( - origin_widget=self._controllers_button).get_root_widget()) + origin_widget=self._controllers_button + ).get_root_widget() + ) def _do_graphics(self) -> None: # pylint: disable=cyclic-import from bastd.ui.settings.graphics import GraphicsSettingsWindow + self._save_state() ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.ui.set_main_menu_window( GraphicsSettingsWindow( - origin_widget=self._graphics_button).get_root_widget()) + origin_widget=self._graphics_button + ).get_root_widget() + ) def _do_audio(self) -> None: # pylint: disable=cyclic-import from bastd.ui.settings.audio import AudioSettingsWindow + self._save_state() ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.ui.set_main_menu_window( AudioSettingsWindow( - origin_widget=self._audio_button).get_root_widget()) + origin_widget=self._audio_button + ).get_root_widget() + ) def _do_advanced(self) -> None: # pylint: disable=cyclic-import from bastd.ui.settings.advanced import AdvancedSettingsWindow + self._save_state() ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.ui.set_main_menu_window( AdvancedSettingsWindow( - origin_widget=self._advanced_button).get_root_widget()) + origin_widget=self._advanced_button + ).get_root_widget() + ) def _save_state(self) -> None: try: @@ -263,8 +309,9 @@ class AllSettingsWindow(ba.Window): def _restore_state(self) -> None: try: - sel_name = ba.app.ui.window_states.get(type(self), - {}).get('sel_name') + sel_name = ba.app.ui.window_states.get(type(self), {}).get( + 'sel_name' + ) sel: ba.Widget | None if sel_name == 'Controllers': sel = self._controllers_button diff --git a/dist/ba_data/python/bastd/ui/settings/audio.py b/dist/ba_data/python/bastd/ui/settings/audio.py index ad464c3..296cc0b 100644 --- a/dist/ba_data/python/bastd/ui/settings/audio.py +++ b/dist/ba_data/python/bastd/ui/settings/audio.py @@ -16,9 +16,11 @@ if TYPE_CHECKING: class AudioSettingsWindow(ba.Window): """Window for editing audio settings.""" - def __init__(self, - transition: str = 'in_right', - origin_widget: ba.Widget | None = None): + def __init__( + self, + transition: str = 'in_right', + origin_widget: ba.Widget | None = None, + ): # pylint: disable=too-many-statements # pylint: disable=too-many-locals # pylint: disable=cyclic-import @@ -57,16 +59,26 @@ class AudioSettingsWindow(ba.Window): height += spacing * 2.0 uiscale = ba.app.ui.uiscale - base_scale = (2.05 if uiscale is ba.UIScale.SMALL else - 1.6 if uiscale is ba.UIScale.MEDIUM else 1.0) + base_scale = ( + 2.05 + if uiscale is ba.UIScale.SMALL + else 1.6 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ) popup_menu_scale = base_scale * 1.2 - super().__init__(root_widget=ba.containerwidget( - size=(width, height), - transition=transition, - scale=base_scale, - scale_origin_stack_offset=scale_origin, - stack_offset=(0, -20) if uiscale is ba.UIScale.SMALL else (0, 0))) + super().__init__( + root_widget=ba.containerwidget( + size=(width, height), + transition=transition, + scale=base_scale, + scale_origin_stack_offset=scale_origin, + stack_offset=(0, -20) + if uiscale is ba.UIScale.SMALL + else (0, 0), + ) + ) self._back_button = back_button = btn = ba.buttonwidget( parent=self._root_widget, @@ -77,23 +89,28 @@ class AudioSettingsWindow(ba.Window): label=ba.Lstr(resource='backText'), button_type='back', on_activate_call=self._back, - autoselect=True) + autoselect=True, + ) ba.containerwidget(edit=self._root_widget, cancel_button=btn) v = height - 60 v -= spacing * 1.0 - ba.textwidget(parent=self._root_widget, - position=(width * 0.5, height - 32), - size=(0, 0), - text=ba.Lstr(resource=self._r + '.titleText'), - color=ba.app.ui.title_color, - maxwidth=180, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(width * 0.5, height - 32), + size=(0, 0), + text=ba.Lstr(resource=self._r + '.titleText'), + color=ba.app.ui.title_color, + maxwidth=180, + h_align='center', + v_align='center', + ) - ba.buttonwidget(edit=self._back_button, - button_type='backSmall', - size=(60, 60), - label=ba.charstr(ba.SpecialChar.BACK)) + ba.buttonwidget( + edit=self._back_button, + button_type='backSmall', + size=(60, 60), + label=ba.charstr(ba.SpecialChar.BACK), + ) self._sound_volume_numedit = svne = ConfigNumberEdit( parent=self._root_widget, @@ -103,11 +120,13 @@ class AudioSettingsWindow(ba.Window): displayname=ba.Lstr(resource=self._r + '.soundVolumeText'), minval=0.0, maxval=1.0, - increment=0.1) + increment=0.1, + ) if ba.app.ui.use_toolbars: ba.widget( edit=svne.plusbutton, - right_widget=ba.internal.get_special_widget('party_button')) + right_widget=ba.internal.get_special_widget('party_button'), + ) v -= spacing self._music_volume_numedit = ConfigNumberEdit( parent=self._root_widget, @@ -119,22 +138,24 @@ class AudioSettingsWindow(ba.Window): maxval=1.0, increment=0.1, callback=music.music_volume_changed, - changesound=False) + changesound=False, + ) v -= 0.5 * spacing self._vr_head_relative_audio_button: ba.Widget | None if show_vr_head_relative_audio: v -= 40 - ba.textwidget(parent=self._root_widget, - position=(40, v + 24), - size=(0, 0), - text=ba.Lstr(resource=self._r + - '.headRelativeVRAudioText'), - color=(0.8, 0.8, 0.8), - maxwidth=230, - h_align='left', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(40, v + 24), + size=(0, 0), + text=ba.Lstr(resource=self._r + '.headRelativeVRAudioText'), + color=(0.8, 0.8, 0.8), + maxwidth=230, + h_align='left', + v_align='center', + ) popup = PopupMenu( parent=self._root_widget, @@ -146,22 +167,24 @@ class AudioSettingsWindow(ba.Window): choices_display=[ ba.Lstr(resource='autoText'), ba.Lstr(resource='onText'), - ba.Lstr(resource='offText') + ba.Lstr(resource='offText'), ], current_choice=ba.app.config.resolve('VR Head Relative Audio'), - on_value_change_call=self._set_vr_head_relative_audio) + on_value_change_call=self._set_vr_head_relative_audio, + ) self._vr_head_relative_audio_button = popup.get_button() - ba.textwidget(parent=self._root_widget, - position=(width * 0.5, v - 11), - size=(0, 0), - text=ba.Lstr(resource=self._r + - '.headRelativeVRAudioInfoText'), - scale=0.5, - color=(0.7, 0.8, 0.7), - maxwidth=400, - flatness=1.0, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(width * 0.5, v - 11), + size=(0, 0), + text=ba.Lstr(resource=self._r + '.headRelativeVRAudioInfoText'), + scale=0.5, + color=(0.7, 0.8, 0.7), + maxwidth=400, + flatness=1.0, + h_align='center', + v_align='center', + ) v -= 30 else: self._vr_head_relative_audio_button = None @@ -175,18 +198,20 @@ class AudioSettingsWindow(ba.Window): size=(310, 50), autoselect=True, label=ba.Lstr(resource=self._r + '.soundtrackButtonText'), - on_activate_call=self._do_soundtracks) + on_activate_call=self._do_soundtracks, + ) v -= spacing * 0.5 - ba.textwidget(parent=self._root_widget, - position=(0, v), - size=(width, 20), - text=ba.Lstr(resource=self._r + - '.soundtrackDescriptionText'), - flatness=1.0, - h_align='center', - scale=0.5, - color=(0.7, 0.8, 0.7, 1.0), - maxwidth=400) + ba.textwidget( + parent=self._root_widget, + position=(0, v), + size=(width, 20), + text=ba.Lstr(resource=self._r + '.soundtrackDescriptionText'), + flatness=1.0, + h_align='center', + scale=0.5, + color=(0.7, 0.8, 0.7, 1.0), + maxwidth=400, + ) else: self._soundtrack_button = None @@ -211,29 +236,38 @@ class AudioSettingsWindow(ba.Window): # if we don't have it, request it. if not ba.internal.have_permission(ba.Permission.STORAGE): ba.playsound(ba.getsound('ding')) - ba.screenmessage(ba.Lstr(resource='storagePermissionAccessText'), - color=(0.5, 1, 0.5)) - ba.timer(1.0, - ba.Call(ba.internal.request_permission, - ba.Permission.STORAGE), - timetype=ba.TimeType.REAL) + ba.screenmessage( + ba.Lstr(resource='storagePermissionAccessText'), + color=(0.5, 1, 0.5), + ) + ba.timer( + 1.0, + ba.Call(ba.internal.request_permission, ba.Permission.STORAGE), + timetype=ba.TimeType.REAL, + ) return self._save_state() ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.ui.set_main_menu_window( stb.SoundtrackBrowserWindow( - origin_widget=self._soundtrack_button).get_root_widget()) + origin_widget=self._soundtrack_button + ).get_root_widget() + ) def _back(self) -> None: # pylint: disable=cyclic-import from bastd.ui.settings import allsettings + self._save_state() - ba.containerwidget(edit=self._root_widget, - transition=self._transition_out) + ba.containerwidget( + edit=self._root_widget, transition=self._transition_out + ) ba.app.ui.set_main_menu_window( allsettings.AllSettingsWindow( - transition='in_left').get_root_widget()) + transition='in_left' + ).get_root_widget() + ) def _save_state(self) -> None: try: diff --git a/dist/ba_data/python/bastd/ui/settings/controls.py b/dist/ba_data/python/bastd/ui/settings/controls.py index 3e9566a..73a2fe3 100644 --- a/dist/ba_data/python/bastd/ui/settings/controls.py +++ b/dist/ba_data/python/bastd/ui/settings/controls.py @@ -16,15 +16,18 @@ if TYPE_CHECKING: class ControlsSettingsWindow(ba.Window): """Top level control settings window.""" - def __init__(self, - transition: str = 'in_right', - origin_widget: ba.Widget | None = None): + def __init__( + self, + transition: str = 'in_right', + origin_widget: ba.Widget | None = None, + ): # FIXME: should tidy up here. # pylint: disable=too-many-statements # pylint: disable=too-many-branches # pylint: disable=too-many-locals # pylint: disable=cyclic-import from bastd.ui import popup as popup_ui + self._have_selected_child = False scale_origin: tuple[float, float] | None @@ -56,8 +59,9 @@ class ControlsSettingsWindow(ba.Window): show_gamepads = False platform = app.platform subplatform = app.subplatform - non_vr_windows = (platform == 'windows' - and (subplatform != 'oculus' or not app.vr_mode)) + non_vr_windows = platform == 'windows' and ( + subplatform != 'oculus' or not app.vr_mode + ) if platform in ('linux', 'android', 'mac') or non_vr_windows: show_gamepads = True height += spacing @@ -73,8 +77,10 @@ class ControlsSettingsWindow(ba.Window): height += space_height show_keyboard = False - if ba.internal.getinputdevice('Keyboard', '#1', - doraise=False) is not None: + if ( + ba.internal.getinputdevice('Keyboard', '#1', doraise=False) + is not None + ): show_keyboard = True height += spacing show_keyboard_p2 = False if app.vr_mode else show_keyboard @@ -112,14 +118,24 @@ class ControlsSettingsWindow(ba.Window): height += spacing uiscale = ba.app.ui.uiscale - smallscale = (1.7 if show_keyboard else 2.2) - super().__init__(root_widget=ba.containerwidget( - size=(width, height), - transition=transition, - scale_origin_stack_offset=scale_origin, - stack_offset=((0, -10) if uiscale is ba.UIScale.SMALL else (0, 0)), - scale=(smallscale if uiscale is ba.UIScale.SMALL else - 1.5 if uiscale is ba.UIScale.MEDIUM else 1.0))) + smallscale = 1.7 if show_keyboard else 2.2 + super().__init__( + root_widget=ba.containerwidget( + size=(width, height), + transition=transition, + scale_origin_stack_offset=scale_origin, + stack_offset=( + (0, -10) if uiscale is ba.UIScale.SMALL else (0, 0) + ), + scale=( + smallscale + if uiscale is ba.UIScale.SMALL + else 1.5 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + ) + ) self._back_button = btn = ba.buttonwidget( parent=self._root_widget, position=(35, height - 60), @@ -129,7 +145,8 @@ class ControlsSettingsWindow(ba.Window): autoselect=True, label=ba.Lstr(resource='backText'), button_type='back', - on_activate_call=self._back) + on_activate_call=self._back, + ) ba.containerwidget(edit=self._root_widget, cancel_button=btn) # We need these vars to exist even if the buttons don't. @@ -139,17 +156,21 @@ class ControlsSettingsWindow(ba.Window): self._keyboard_2_button: ba.Widget | None = None self._idevices_button: ba.Widget | None = None - ba.textwidget(parent=self._root_widget, - position=(0, height - 49), - size=(width, 25), - text=ba.Lstr(resource=self._r + '.titleText'), - color=ba.app.ui.title_color, - h_align='center', - v_align='top') - ba.buttonwidget(edit=btn, - button_type='backSmall', - size=(60, 60), - label=ba.charstr(ba.SpecialChar.BACK)) + ba.textwidget( + parent=self._root_widget, + position=(0, height - 49), + size=(width, 25), + text=ba.Lstr(resource=self._r + '.titleText'), + color=ba.app.ui.title_color, + h_align='center', + v_align='top', + ) + ba.buttonwidget( + edit=btn, + button_type='backSmall', + size=(60, 60), + label=ba.charstr(ba.SpecialChar.BACK), + ) v = height - 75 v -= spacing @@ -161,16 +182,20 @@ class ControlsSettingsWindow(ba.Window): size=(button_width, 43), autoselect=True, label=ba.Lstr(resource=self._r + '.configureTouchText'), - on_activate_call=self._do_touchscreen) + on_activate_call=self._do_touchscreen, + ) if ba.app.ui.use_toolbars: - ba.widget(edit=btn, - right_widget=ba.internal.get_special_widget( - 'party_button')) + ba.widget( + edit=btn, + right_widget=ba.internal.get_special_widget('party_button'), + ) if not self._have_selected_child: - ba.containerwidget(edit=self._root_widget, - selected_child=self._touch_button) - ba.widget(edit=self._back_button, - down_widget=self._touch_button) + ba.containerwidget( + edit=self._root_widget, selected_child=self._touch_button + ) + ba.widget( + edit=self._back_button, down_widget=self._touch_button + ) self._have_selected_child = True v -= spacing @@ -181,16 +206,20 @@ class ControlsSettingsWindow(ba.Window): size=(button_width, 43), autoselect=True, label=ba.Lstr(resource=self._r + '.configureControllersText'), - on_activate_call=self._do_gamepads) + on_activate_call=self._do_gamepads, + ) if ba.app.ui.use_toolbars: - ba.widget(edit=btn, - right_widget=ba.internal.get_special_widget( - 'party_button')) + ba.widget( + edit=btn, + right_widget=ba.internal.get_special_widget('party_button'), + ) if not self._have_selected_child: - ba.containerwidget(edit=self._root_widget, - selected_child=self._gamepads_button) - ba.widget(edit=self._back_button, - down_widget=self._gamepads_button) + ba.containerwidget( + edit=self._root_widget, selected_child=self._gamepads_button + ) + ba.widget( + edit=self._back_button, down_widget=self._gamepads_button + ) self._have_selected_child = True v -= spacing else: @@ -206,16 +235,20 @@ class ControlsSettingsWindow(ba.Window): size=(button_width, 43), autoselect=True, label=ba.Lstr(resource=self._r + '.configureKeyboardText'), - on_activate_call=self._config_keyboard) + on_activate_call=self._config_keyboard, + ) if ba.app.ui.use_toolbars: - ba.widget(edit=btn, - right_widget=ba.internal.get_special_widget( - 'party_button')) + ba.widget( + edit=btn, + right_widget=ba.internal.get_special_widget('party_button'), + ) if not self._have_selected_child: - ba.containerwidget(edit=self._root_widget, - selected_child=self._keyboard_button) - ba.widget(edit=self._back_button, - down_widget=self._keyboard_button) + ba.containerwidget( + edit=self._root_widget, selected_child=self._keyboard_button + ) + ba.widget( + edit=self._back_button, down_widget=self._keyboard_button + ) self._have_selected_child = True v -= spacing if show_keyboard_p2: @@ -225,7 +258,8 @@ class ControlsSettingsWindow(ba.Window): size=(button_width, 43), autoselect=True, label=ba.Lstr(resource=self._r + '.configureKeyboard2Text'), - on_activate_call=self._config_keyboard2) + on_activate_call=self._config_keyboard2, + ) v -= spacing if show_space_2: v -= space_height @@ -236,16 +270,20 @@ class ControlsSettingsWindow(ba.Window): size=(button_width, 43), autoselect=True, label=ba.Lstr(resource=self._r + '.configureMobileText'), - on_activate_call=self._do_mobile_devices) + on_activate_call=self._do_mobile_devices, + ) if ba.app.ui.use_toolbars: - ba.widget(edit=btn, - right_widget=ba.internal.get_special_widget( - 'party_button')) + ba.widget( + edit=btn, + right_widget=ba.internal.get_special_widget('party_button'), + ) if not self._have_selected_child: - ba.containerwidget(edit=self._root_widget, - selected_child=self._idevices_button) - ba.widget(edit=self._back_button, - down_widget=self._idevices_button) + ba.containerwidget( + edit=self._root_widget, selected_child=self._idevices_button + ) + ba.widget( + edit=self._back_button, down_widget=self._idevices_button + ) self._have_selected_child = True v -= spacing @@ -254,21 +292,27 @@ class ControlsSettingsWindow(ba.Window): def do_toggle(value: bool) -> None: ba.screenmessage( ba.Lstr(resource='settingsWindowAdvanced.mustRestartText'), - color=(1, 1, 0)) + color=(1, 1, 0), + ) ba.playsound(ba.getsound('gunCocking')) - ba.internal.set_low_level_config_value('enablexinput', - not value) + ba.internal.set_low_level_config_value( + 'enablexinput', not value + ) ba.checkboxwidget( parent=self._root_widget, position=(100, v + 3), size=(120, 30), - value=(not ba.internal.get_low_level_config_value( - 'enablexinput', 1)), + value=( + not ba.internal.get_low_level_config_value( + 'enablexinput', 1 + ) + ), maxwidth=200, on_value_change_call=do_toggle, text=ba.Lstr(resource='disableXInputText'), - autoselect=True) + autoselect=True, + ) ba.textwidget( parent=self._root_widget, position=(width * 0.5, v - 5), @@ -278,7 +322,8 @@ class ControlsSettingsWindow(ba.Window): h_align='center', v_align='center', color=ba.app.ui.infotextcolor, - maxwidth=width * 0.8) + maxwidth=width * 0.8, + ) v -= spacing if show_mac_controller_subsystem: popup_ui.PopupMenu( @@ -291,11 +336,13 @@ class ControlsSettingsWindow(ba.Window): choices_display=[ ba.Lstr(resource='macControllerSubsystemClassicText'), ba.Lstr(resource='macControllerSubsystemMFiText'), - ba.Lstr(resource='macControllerSubsystemBothText') + ba.Lstr(resource='macControllerSubsystemBothText'), ], current_choice=ba.app.config.resolve( - 'Mac Controller Subsystem'), - on_value_change_call=self._set_mac_controller_subsystem) + 'Mac Controller Subsystem' + ), + on_value_change_call=self._set_mac_controller_subsystem, + ) ba.textwidget( parent=self._root_widget, position=(245, v + 13), @@ -305,7 +352,8 @@ class ControlsSettingsWindow(ba.Window): h_align='right', v_align='center', color=ba.app.ui.infotextcolor, - maxwidth=180) + maxwidth=180, + ) ba.textwidget( parent=self._root_widget, position=(width * 0.5, v - 20), @@ -315,7 +363,8 @@ class ControlsSettingsWindow(ba.Window): h_align='center', v_align='center', color=ba.app.ui.infotextcolor, - maxwidth=width * 0.8) + maxwidth=width * 0.8, + ) v -= spacing * 1.5 self._restore_state() @@ -327,32 +376,41 @@ class ControlsSettingsWindow(ba.Window): def _config_keyboard(self) -> None: # pylint: disable=cyclic-import from bastd.ui.settings.keyboard import ConfigKeyboardWindow + self._save_state() ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.ui.set_main_menu_window( - ConfigKeyboardWindow(ba.internal.getinputdevice( - 'Keyboard', '#1')).get_root_widget()) + ConfigKeyboardWindow( + ba.internal.getinputdevice('Keyboard', '#1') + ).get_root_widget() + ) def _config_keyboard2(self) -> None: # pylint: disable=cyclic-import from bastd.ui.settings.keyboard import ConfigKeyboardWindow + self._save_state() ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.ui.set_main_menu_window( - ConfigKeyboardWindow(ba.internal.getinputdevice( - 'Keyboard', '#2')).get_root_widget()) + ConfigKeyboardWindow( + ba.internal.getinputdevice('Keyboard', '#2') + ).get_root_widget() + ) def _do_mobile_devices(self) -> None: # pylint: disable=cyclic-import from bastd.ui.settings.remoteapp import RemoteAppSettingsWindow + self._save_state() ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.ui.set_main_menu_window( - RemoteAppSettingsWindow().get_root_widget()) + RemoteAppSettingsWindow().get_root_widget() + ) def _do_gamepads(self) -> None: # pylint: disable=cyclic-import from bastd.ui.settings.gamepadselect import GamepadSelectWindow + self._save_state() ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.ui.set_main_menu_window(GamepadSelectWindow().get_root_widget()) @@ -360,10 +418,12 @@ class ControlsSettingsWindow(ba.Window): def _do_touchscreen(self) -> None: # pylint: disable=cyclic-import from bastd.ui.settings.touchscreen import TouchscreenSettingsWindow + self._save_state() ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.ui.set_main_menu_window( - TouchscreenSettingsWindow().get_root_widget()) + TouchscreenSettingsWindow().get_root_widget() + ) def _save_state(self) -> None: sel = self._root_widget.get_selected_child() @@ -396,15 +456,21 @@ class ControlsSettingsWindow(ba.Window): elif sel_name == 'Back': sel = self._back_button else: - sel = (self._gamepads_button - if self._gamepads_button is not None else self._back_button) + sel = ( + self._gamepads_button + if self._gamepads_button is not None + else self._back_button + ) ba.containerwidget(edit=self._root_widget, selected_child=sel) def _back(self) -> None: # pylint: disable=cyclic-import from bastd.ui.settings.allsettings import AllSettingsWindow + self._save_state() - ba.containerwidget(edit=self._root_widget, - transition=self._transition_out) + ba.containerwidget( + edit=self._root_widget, transition=self._transition_out + ) ba.app.ui.set_main_menu_window( - AllSettingsWindow(transition='in_left').get_root_widget()) + AllSettingsWindow(transition='in_left').get_root_widget() + ) diff --git a/dist/ba_data/python/bastd/ui/settings/gamepad.py b/dist/ba_data/python/bastd/ui/settings/gamepad.py index 4d5d946..cbb58a7 100644 --- a/dist/ba_data/python/bastd/ui/settings/gamepad.py +++ b/dist/ba_data/python/bastd/ui/settings/gamepad.py @@ -16,12 +16,14 @@ if TYPE_CHECKING: class GamepadSettingsWindow(ba.Window): """Window for configuring a gamepad.""" - def __init__(self, - gamepad: ba.InputDevice, - is_main_menu: bool = True, - transition: str = 'in_right', - transition_out: str = 'out_right', - settings: dict | None = None): + def __init__( + self, + gamepad: ba.InputDevice, + is_main_menu: bool = True, + transition: str = 'in_right', + transition_out: str = 'out_right', + settings: dict | None = None, + ): self._input = gamepad # If our input-device went away, just return an empty zombie. @@ -35,7 +37,7 @@ class GamepadSettingsWindow(ba.Window): self._transition_out = transition_out # We're a secondary gamepad if supplied with settings. - self._is_secondary = (settings is not None) + self._is_secondary = settings is not None self._ext = '_B' if self._is_secondary else '' self._is_main_menu = is_main_menu self._displayname = self._name @@ -43,12 +45,22 @@ class GamepadSettingsWindow(ba.Window): self._height = 440 if self._is_secondary else 450 self._spacing = 40 uiscale = ba.app.ui.uiscale - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height), - scale=(1.63 if uiscale is ba.UIScale.SMALL else - 1.35 if uiscale is ba.UIScale.MEDIUM else 1.0), - stack_offset=(-20, -16) if uiscale is ba.UIScale.SMALL else (0, 0), - transition=transition)) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height), + scale=( + 1.63 + if uiscale is ba.UIScale.SMALL + else 1.35 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + stack_offset=(-20, -16) + if uiscale is ba.UIScale.SMALL + else (0, 0), + transition=transition, + ) + ) # Don't ask to config joysticks while we're in here. self._rebuild_ui() @@ -72,63 +84,63 @@ class GamepadSettingsWindow(ba.Window): # secondary controls). self._settings = {} for skey in [ - 'buttonJump', - 'buttonJump_B', - 'buttonPunch', - 'buttonPunch_B', - 'buttonBomb', - 'buttonBomb_B', - 'buttonPickUp', - 'buttonPickUp_B', - 'buttonStart', - 'buttonStart_B', - 'buttonStart2', - 'buttonStart2_B', - 'buttonUp', - 'buttonUp_B', - 'buttonDown', - 'buttonDown_B', - 'buttonLeft', - 'buttonLeft_B', - 'buttonRight', - 'buttonRight_B', - 'buttonRun1', - 'buttonRun1_B', - 'buttonRun2', - 'buttonRun2_B', - 'triggerRun1', - 'triggerRun1_B', - 'triggerRun2', - 'triggerRun2_B', - 'buttonIgnored', - 'buttonIgnored_B', - 'buttonIgnored2', - 'buttonIgnored2_B', - 'buttonIgnored3', - 'buttonIgnored3_B', - 'buttonIgnored4', - 'buttonIgnored4_B', - 'buttonVRReorient', - 'buttonVRReorient_B', - 'analogStickDeadZone', - 'analogStickDeadZone_B', - 'dpad', - 'dpad_B', - 'unassignedButtonsRun', - 'unassignedButtonsRun_B', - 'startButtonActivatesDefaultWidget', - 'startButtonActivatesDefaultWidget_B', - 'uiOnly', - 'uiOnly_B', - 'ignoreCompletely', - 'ignoreCompletely_B', - 'autoRecalibrateAnalogStick', - 'autoRecalibrateAnalogStick_B', - 'analogStickLR', - 'analogStickLR_B', - 'analogStickUD', - 'analogStickUD_B', - 'enableSecondary', + 'buttonJump', + 'buttonJump_B', + 'buttonPunch', + 'buttonPunch_B', + 'buttonBomb', + 'buttonBomb_B', + 'buttonPickUp', + 'buttonPickUp_B', + 'buttonStart', + 'buttonStart_B', + 'buttonStart2', + 'buttonStart2_B', + 'buttonUp', + 'buttonUp_B', + 'buttonDown', + 'buttonDown_B', + 'buttonLeft', + 'buttonLeft_B', + 'buttonRight', + 'buttonRight_B', + 'buttonRun1', + 'buttonRun1_B', + 'buttonRun2', + 'buttonRun2_B', + 'triggerRun1', + 'triggerRun1_B', + 'triggerRun2', + 'triggerRun2_B', + 'buttonIgnored', + 'buttonIgnored_B', + 'buttonIgnored2', + 'buttonIgnored2_B', + 'buttonIgnored3', + 'buttonIgnored3_B', + 'buttonIgnored4', + 'buttonIgnored4_B', + 'buttonVRReorient', + 'buttonVRReorient_B', + 'analogStickDeadZone', + 'analogStickDeadZone_B', + 'dpad', + 'dpad_B', + 'unassignedButtonsRun', + 'unassignedButtonsRun_B', + 'startButtonActivatesDefaultWidget', + 'startButtonActivatesDefaultWidget_B', + 'uiOnly', + 'uiOnly_B', + 'ignoreCompletely', + 'ignoreCompletely_B', + 'autoRecalibrateAnalogStick', + 'autoRecalibrateAnalogStick_B', + 'analogStickLR', + 'analogStickLR_B', + 'analogStickUD', + 'analogStickUD_B', + 'enableSecondary', ]: val = get_device_value(self._input, skey) if val != -1: @@ -137,17 +149,20 @@ class GamepadSettingsWindow(ba.Window): back_button: ba.Widget | None if self._is_secondary: - back_button = ba.buttonwidget(parent=self._root_widget, - position=(self._width - 180, - self._height - 65), - autoselect=True, - size=(160, 60), - label=ba.Lstr(resource='doneText'), - scale=0.9, - on_activate_call=self._save) - ba.containerwidget(edit=self._root_widget, - start_button=back_button, - on_cancel_call=back_button.activate) + back_button = ba.buttonwidget( + parent=self._root_widget, + position=(self._width - 180, self._height - 65), + autoselect=True, + size=(160, 60), + label=ba.Lstr(resource='doneText'), + scale=0.9, + on_activate_call=self._save, + ) + ba.containerwidget( + edit=self._root_widget, + start_button=back_button, + on_cancel_call=back_button.activate, + ) cancel_button = None else: cancel_button = ba.buttonwidget( @@ -157,80 +172,95 @@ class GamepadSettingsWindow(ba.Window): size=(160, 60), label=ba.Lstr(resource='cancelText'), scale=0.9, - on_activate_call=self._cancel) - ba.containerwidget(edit=self._root_widget, - cancel_button=cancel_button) + on_activate_call=self._cancel, + ) + ba.containerwidget( + edit=self._root_widget, cancel_button=cancel_button + ) save_button: ba.Widget | None if not self._is_secondary: save_button = ba.buttonwidget( parent=self._root_widget, - position=(self._width - (165 if self._is_secondary else 195), - self._height - 65), + position=( + self._width - (165 if self._is_secondary else 195), + self._height - 65, + ), size=((160 if self._is_secondary else 180), 60), autoselect=True, label=ba.Lstr(resource='doneText') - if self._is_secondary else ba.Lstr(resource='saveText'), + if self._is_secondary + else ba.Lstr(resource='saveText'), scale=0.9, - on_activate_call=self._save) - ba.containerwidget(edit=self._root_widget, - start_button=save_button) + on_activate_call=self._save, + ) + ba.containerwidget(edit=self._root_widget, start_button=save_button) else: save_button = None if not self._is_secondary: v = self._height - 59 - ba.textwidget(parent=self._root_widget, - position=(0, v + 5), - size=(self._width, 25), - text=ba.Lstr(resource=self._r + '.titleText'), - color=ba.app.ui.title_color, - maxwidth=310, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(0, v + 5), + size=(self._width, 25), + text=ba.Lstr(resource=self._r + '.titleText'), + color=ba.app.ui.title_color, + maxwidth=310, + h_align='center', + v_align='center', + ) v -= 48 - ba.textwidget(parent=self._root_widget, - position=(0, v + 3), - size=(self._width, 25), - text=self._name, - color=ba.app.ui.infotextcolor, - maxwidth=self._width * 0.9, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(0, v + 3), + size=(self._width, 25), + text=self._name, + color=ba.app.ui.infotextcolor, + maxwidth=self._width * 0.9, + h_align='center', + v_align='center', + ) v -= self._spacing * 1 - ba.textwidget(parent=self._root_widget, - position=(50, v + 10), - size=(self._width - 100, 30), - text=ba.Lstr(resource=self._r + '.appliesToAllText'), - maxwidth=330, - scale=0.65, - color=(0.5, 0.6, 0.5, 1.0), - h_align='center', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(50, v + 10), + size=(self._width - 100, 30), + text=ba.Lstr(resource=self._r + '.appliesToAllText'), + maxwidth=330, + scale=0.65, + color=(0.5, 0.6, 0.5, 1.0), + h_align='center', + v_align='center', + ) v -= 70 self._enable_check_box = None else: v = self._height - 49 - ba.textwidget(parent=self._root_widget, - position=(0, v + 5), - size=(self._width, 25), - text=ba.Lstr(resource=self._r + '.secondaryText'), - color=ba.app.ui.title_color, - maxwidth=300, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(0, v + 5), + size=(self._width, 25), + text=ba.Lstr(resource=self._r + '.secondaryText'), + color=ba.app.ui.title_color, + maxwidth=300, + h_align='center', + v_align='center', + ) v -= self._spacing * 1 - ba.textwidget(parent=self._root_widget, - position=(50, v + 10), - size=(self._width - 100, 30), - text=ba.Lstr(resource=self._r + '.secondHalfText'), - maxwidth=300, - scale=0.65, - color=(0.6, 0.8, 0.6, 1.0), - h_align='center') + ba.textwidget( + parent=self._root_widget, + position=(50, v + 10), + size=(self._width - 100, 30), + text=ba.Lstr(resource=self._r + '.secondHalfText'), + maxwidth=300, + scale=0.65, + color=(0.6, 0.8, 0.6, 1.0), + h_align='center', + ) self._enable_check_box = ba.checkboxwidget( parent=self._root_widget, position=(self._width * 0.5 - 80, v - 73), @@ -239,7 +269,8 @@ class GamepadSettingsWindow(ba.Window): on_value_change_call=self._enable_check_box_changed, size=(200, 30), text=ba.Lstr(resource=self._r + '.secondaryEnableText'), - scale=1.2) + scale=1.2, + ) v = self._height - 205 h_offs = 160 @@ -249,74 +280,93 @@ class GamepadSettingsWindow(ba.Window): scly = 0.98 dpm = ba.Lstr(resource=self._r + '.pressAnyButtonOrDpadText') dpm2 = ba.Lstr(resource=self._r + '.ifNothingHappensTryAnalogText') - self._capture_button(pos=(h_offs, v + scly * dist), - color=d_color, - button='buttonUp' + self._ext, - texture=ba.gettexture('upButton'), - scale=1.0, - message=dpm, - message2=dpm2) - self._capture_button(pos=(h_offs - sclx * dist, v), - color=d_color, - button='buttonLeft' + self._ext, - texture=ba.gettexture('leftButton'), - scale=1.0, - message=dpm, - message2=dpm2) - self._capture_button(pos=(h_offs + sclx * dist, v), - color=d_color, - button='buttonRight' + self._ext, - texture=ba.gettexture('rightButton'), - scale=1.0, - message=dpm, - message2=dpm2) - self._capture_button(pos=(h_offs, v - scly * dist), - color=d_color, - button='buttonDown' + self._ext, - texture=ba.gettexture('downButton'), - scale=1.0, - message=dpm, - message2=dpm2) + self._capture_button( + pos=(h_offs, v + scly * dist), + color=d_color, + button='buttonUp' + self._ext, + texture=ba.gettexture('upButton'), + scale=1.0, + message=dpm, + message2=dpm2, + ) + self._capture_button( + pos=(h_offs - sclx * dist, v), + color=d_color, + button='buttonLeft' + self._ext, + texture=ba.gettexture('leftButton'), + scale=1.0, + message=dpm, + message2=dpm2, + ) + self._capture_button( + pos=(h_offs + sclx * dist, v), + color=d_color, + button='buttonRight' + self._ext, + texture=ba.gettexture('rightButton'), + scale=1.0, + message=dpm, + message2=dpm2, + ) + self._capture_button( + pos=(h_offs, v - scly * dist), + color=d_color, + button='buttonDown' + self._ext, + texture=ba.gettexture('downButton'), + scale=1.0, + message=dpm, + message2=dpm2, + ) dpm3 = ba.Lstr(resource=self._r + '.ifNothingHappensTryDpadText') - self._capture_button(pos=(h_offs + 130, v - 125), - color=(0.4, 0.4, 0.6), - button='analogStickLR' + self._ext, - maxwidth=140, - texture=ba.gettexture('analogStick'), - scale=1.2, - message=ba.Lstr(resource=self._r + - '.pressLeftRightText'), - message2=dpm3) + self._capture_button( + pos=(h_offs + 130, v - 125), + color=(0.4, 0.4, 0.6), + button='analogStickLR' + self._ext, + maxwidth=140, + texture=ba.gettexture('analogStick'), + scale=1.2, + message=ba.Lstr(resource=self._r + '.pressLeftRightText'), + message2=dpm3, + ) - self._capture_button(pos=(self._width * 0.5, v), - color=(0.4, 0.4, 0.6), - button='buttonStart' + self._ext, - texture=ba.gettexture('startButton'), - scale=0.7) + self._capture_button( + pos=(self._width * 0.5, v), + color=(0.4, 0.4, 0.6), + button='buttonStart' + self._ext, + texture=ba.gettexture('startButton'), + scale=0.7, + ) h_offs = self._width - 160 - self._capture_button(pos=(h_offs, v + scly * dist), - color=(0.6, 0.4, 0.8), - button='buttonPickUp' + self._ext, - texture=ba.gettexture('buttonPickUp'), - scale=1.0) - self._capture_button(pos=(h_offs - sclx * dist, v), - color=(0.7, 0.5, 0.1), - button='buttonPunch' + self._ext, - texture=ba.gettexture('buttonPunch'), - scale=1.0) - self._capture_button(pos=(h_offs + sclx * dist, v), - color=(0.5, 0.2, 0.1), - button='buttonBomb' + self._ext, - texture=ba.gettexture('buttonBomb'), - scale=1.0) - self._capture_button(pos=(h_offs, v - scly * dist), - color=(0.2, 0.5, 0.2), - button='buttonJump' + self._ext, - texture=ba.gettexture('buttonJump'), - scale=1.0) + self._capture_button( + pos=(h_offs, v + scly * dist), + color=(0.6, 0.4, 0.8), + button='buttonPickUp' + self._ext, + texture=ba.gettexture('buttonPickUp'), + scale=1.0, + ) + self._capture_button( + pos=(h_offs - sclx * dist, v), + color=(0.7, 0.5, 0.1), + button='buttonPunch' + self._ext, + texture=ba.gettexture('buttonPunch'), + scale=1.0, + ) + self._capture_button( + pos=(h_offs + sclx * dist, v), + color=(0.5, 0.2, 0.1), + button='buttonBomb' + self._ext, + texture=ba.gettexture('buttonBomb'), + scale=1.0, + ) + self._capture_button( + pos=(h_offs, v - scly * dist), + color=(0.2, 0.5, 0.2), + button='buttonJump' + self._ext, + texture=ba.gettexture('buttonJump'), + scale=1.0, + ) self._advanced_button = ba.buttonwidget( parent=self._root_widget, @@ -327,7 +377,8 @@ class GamepadSettingsWindow(ba.Window): textcolor=(0.65, 0.6, 0.7), position=(self._width - 300, 30), size=(130, 40), - on_activate_call=self._do_advanced) + on_activate_call=self._do_advanced, + ) try: if cancel_button is not None and save_button is not None: @@ -364,6 +415,7 @@ class GamepadSettingsWindow(ba.Window): def _do_advanced(self) -> None: # pylint: disable=cyclic-import from bastd.ui.settings import gamepadadvanced + gamepadadvanced.GamepadAdvancedSettingsWindow(self) def _enable_check_box_changed(self, value: bool) -> None: @@ -396,8 +448,9 @@ class GamepadSettingsWindow(ba.Window): assert self._settings is not None return self._settings.get('startButtonActivatesDefaultWidget', True) - def set_start_button_activates_default_widget_value(self, - value: bool) -> None: + def set_start_button_activates_default_widget_value( + self, value: bool + ) -> None: """(internal)""" assert self._settings is not None if value: @@ -465,11 +518,13 @@ class GamepadSettingsWindow(ba.Window): def show_secondary_editor(self) -> None: """(internal)""" - GamepadSettingsWindow(self._input, - is_main_menu=False, - settings=self._settings, - transition='in_scale', - transition_out='out_scale') + GamepadSettingsWindow( + self._input, + is_main_menu=False, + settings=self._settings, + transition='in_scale', + transition_out='out_scale', + ) def get_control_value_name(self, control: str) -> str | ba.Lstr: """(internal)""" @@ -478,14 +533,25 @@ class GamepadSettingsWindow(ba.Window): if control == 'analogStickLR' + self._ext: # This actually shows both LR and UD. - sval1 = (self._settings['analogStickLR' + - self._ext] if 'analogStickLR' + self._ext - in self._settings else 5 if self._is_secondary else 1) - sval2 = (self._settings['analogStickUD' + - self._ext] if 'analogStickUD' + self._ext - in self._settings else 6 if self._is_secondary else 2) - return self._input.get_axis_name( - sval1) + ' / ' + self._input.get_axis_name(sval2) + sval1 = ( + self._settings['analogStickLR' + self._ext] + if 'analogStickLR' + self._ext in self._settings + else 5 + if self._is_secondary + else 1 + ) + sval2 = ( + self._settings['analogStickUD' + self._ext] + if 'analogStickUD' + self._ext in self._settings + else 6 + if self._is_secondary + else 2 + ) + return ( + self._input.get_axis_name(sval1) + + ' / ' + + self._input.get_axis_name(sval2) + ) # If they're looking for triggers. if control in ['triggerRun1' + self._ext, 'triggerRun2' + self._ext]: @@ -502,8 +568,10 @@ class GamepadSettingsWindow(ba.Window): # For dpad buttons: show individual buttons if any are set. # Otherwise show whichever dpad is set (defaulting to 1). dpad_buttons = [ - 'buttonLeft' + self._ext, 'buttonRight' + self._ext, - 'buttonUp' + self._ext, 'buttonDown' + self._ext + 'buttonLeft' + self._ext, + 'buttonRight' + self._ext, + 'buttonUp' + self._ext, + 'buttonDown' + self._ext, ] if control in dpad_buttons: @@ -516,20 +584,32 @@ class GamepadSettingsWindow(ba.Window): # No dpad buttons - show the dpad number for all 4. return ba.Lstr( value='${A} ${B}', - subs=[('${A}', ba.Lstr(resource=self._r + '.dpadText')), - ('${B}', - str(self._settings['dpad' + - self._ext] if 'dpad' + self._ext in - self._settings else 2 if self._is_secondary else 1)) - ]) + subs=[ + ('${A}', ba.Lstr(resource=self._r + '.dpadText')), + ( + '${B}', + str( + self._settings['dpad' + self._ext] + if 'dpad' + self._ext in self._settings + else 2 + if self._is_secondary + else 1 + ), + ), + ], + ) # other buttons.. if control in self._settings: return self._input.get_button_name(self._settings[control]) return ba.Lstr(resource=self._r + '.unsetText') - def _gamepad_event(self, control: str, event: dict[str, Any], - dialog: AwaitGamepadInputWindow) -> None: + def _gamepad_event( + self, + control: str, + event: dict[str, Any], + dialog: AwaitGamepadInputWindow, + ) -> None: # pylint: disable=too-many-nested-blocks # pylint: disable=too-many-branches # pylint: disable=too-many-statements @@ -539,8 +619,10 @@ class GamepadSettingsWindow(ba.Window): # For our dpad-buttons we're looking for either a button-press or a # hat-switch press. if control in [ - 'buttonUp' + ext, 'buttonLeft' + ext, 'buttonDown' + ext, - 'buttonRight' + ext + 'buttonUp' + ext, + 'buttonLeft' + ext, + 'buttonDown' + ext, + 'buttonRight' + ext, ]: if event['type'] in ['BUTTONDOWN', 'HATMOTION']: @@ -553,8 +635,10 @@ class GamepadSettingsWindow(ba.Window): elif event['type'] == 'HATMOTION': # clear out any set dir-buttons for btn in [ - 'buttonUp' + ext, 'buttonLeft' + ext, - 'buttonRight' + ext, 'buttonDown' + ext + 'buttonUp' + ext, + 'buttonLeft' + ext, + 'buttonRight' + ext, + 'buttonDown' + ext, ]: if btn in self._settings: del self._settings[btn] @@ -567,18 +651,22 @@ class GamepadSettingsWindow(ba.Window): self._settings['dpad' + ext] = event['hat'] # Update the 4 dpad button txt widgets. - ba.textwidget(edit=self._textwidgets['buttonUp' + ext], - text=self.get_control_value_name('buttonUp' + - ext)) - ba.textwidget(edit=self._textwidgets['buttonLeft' + ext], - text=self.get_control_value_name('buttonLeft' + - ext)) - ba.textwidget(edit=self._textwidgets['buttonRight' + ext], - text=self.get_control_value_name('buttonRight' + - ext)) - ba.textwidget(edit=self._textwidgets['buttonDown' + ext], - text=self.get_control_value_name('buttonDown' + - ext)) + ba.textwidget( + edit=self._textwidgets['buttonUp' + ext], + text=self.get_control_value_name('buttonUp' + ext), + ) + ba.textwidget( + edit=self._textwidgets['buttonLeft' + ext], + text=self.get_control_value_name('buttonLeft' + ext), + ) + ba.textwidget( + edit=self._textwidgets['buttonRight' + ext], + text=self.get_control_value_name('buttonRight' + ext), + ) + ba.textwidget( + edit=self._textwidgets['buttonDown' + ext], + text=self.get_control_value_name('buttonDown' + ext), + ) ba.playsound(ba.getsound('gunCocking')) dialog.die() @@ -597,16 +685,18 @@ class GamepadSettingsWindow(ba.Window): self._settings['analogStickLR' + ext] = axis ba.textwidget( edit=self._textwidgets['analogStickLR' + ext], - text=self.get_control_value_name('analogStickLR' + - ext)) + text=self.get_control_value_name('analogStickLR' + ext), + ) ba.playsound(ba.getsound('gunCocking')) dialog.die() # Now launch the up/down listener. AwaitGamepadInputWindow( - self._input, 'analogStickUD' + ext, + self._input, + 'analogStickUD' + ext, self._gamepad_event, - ba.Lstr(resource=self._r + '.pressUpDownText')) + ba.Lstr(resource=self._r + '.pressUpDownText'), + ) elif control == 'analogStickUD' + ext: if event['type'] == 'AXISMOTION': @@ -619,7 +709,7 @@ class GamepadSettingsWindow(ba.Window): if 'analogStickLR' + ext in self._settings: lr_axis = self._settings['analogStickLR' + ext] else: - lr_axis = (5 if self._is_secondary else 1) + lr_axis = 5 if self._is_secondary else 1 if axis != lr_axis: if axis == (6 if self._is_secondary else 2): @@ -630,8 +720,10 @@ class GamepadSettingsWindow(ba.Window): self._settings['analogStickUD' + ext] = axis ba.textwidget( edit=self._textwidgets['analogStickLR' + ext], - text=self.get_control_value_name('analogStickLR' + - ext)) + text=self.get_control_value_name( + 'analogStickLR' + ext + ), + ) ba.playsound(ba.getsound('gunCocking')) dialog.die() else: @@ -641,69 +733,93 @@ class GamepadSettingsWindow(ba.Window): self._settings[control] = value # Update the button's text widget. - ba.textwidget(edit=self._textwidgets[control], - text=self.get_control_value_name(control)) + ba.textwidget( + edit=self._textwidgets[control], + text=self.get_control_value_name(control), + ) ba.playsound(ba.getsound('gunCocking')) dialog.die() - def _capture_button(self, - pos: tuple[float, float], - color: tuple[float, float, float], - texture: ba.Texture, - button: str, - scale: float = 1.0, - message: ba.Lstr | None = None, - message2: ba.Lstr | None = None, - maxwidth: float = 80.0) -> ba.Widget: + def _capture_button( + self, + pos: tuple[float, float], + color: tuple[float, float, float], + texture: ba.Texture, + button: str, + scale: float = 1.0, + message: ba.Lstr | None = None, + message2: ba.Lstr | None = None, + maxwidth: float = 80.0, + ) -> ba.Widget: if message is None: message = ba.Lstr(resource=self._r + '.pressAnyButtonText') base_size = 79 - btn = ba.buttonwidget(parent=self._root_widget, - position=(pos[0] - base_size * 0.5 * scale, - pos[1] - base_size * 0.5 * scale), - autoselect=True, - size=(base_size * scale, base_size * scale), - texture=texture, - label='', - color=color) + btn = ba.buttonwidget( + parent=self._root_widget, + position=( + pos[0] - base_size * 0.5 * scale, + pos[1] - base_size * 0.5 * scale, + ), + autoselect=True, + size=(base_size * scale, base_size * scale), + texture=texture, + label='', + color=color, + ) # Make this in a timer so that it shows up on top of all other buttons. def doit() -> None: uiscale = 0.9 * scale - txt = ba.textwidget(parent=self._root_widget, - position=(pos[0] + 0.0 * scale, - pos[1] - 58.0 * scale), - color=(1, 1, 1, 0.3), - size=(0, 0), - h_align='center', - v_align='center', - scale=uiscale, - text=self.get_control_value_name(button), - maxwidth=maxwidth) + txt = ba.textwidget( + parent=self._root_widget, + position=(pos[0] + 0.0 * scale, pos[1] - 58.0 * scale), + color=(1, 1, 1, 0.3), + size=(0, 0), + h_align='center', + v_align='center', + scale=uiscale, + text=self.get_control_value_name(button), + maxwidth=maxwidth, + ) self._textwidgets[button] = txt - ba.buttonwidget(edit=btn, - on_activate_call=ba.Call(AwaitGamepadInputWindow, - self._input, button, - self._gamepad_event, - message, message2)) + ba.buttonwidget( + edit=btn, + on_activate_call=ba.Call( + AwaitGamepadInputWindow, + self._input, + button, + self._gamepad_event, + message, + message2, + ), + ) ba.timer(0, doit, timetype=ba.TimeType.REAL) return btn def _cancel(self) -> None: from bastd.ui.settings.controls import ControlsSettingsWindow - ba.containerwidget(edit=self._root_widget, - transition=self._transition_out) + + ba.containerwidget( + edit=self._root_widget, transition=self._transition_out + ) if self._is_main_menu: ba.app.ui.set_main_menu_window( - ControlsSettingsWindow(transition='in_left').get_root_widget()) + ControlsSettingsWindow(transition='in_left').get_root_widget() + ) def _save(self) -> None: - from ba.internal import (master_server_post, get_input_device_config, - get_input_map_hash, should_submit_debug_info) - ba.containerwidget(edit=self._root_widget, - transition=self._transition_out) + from ba.internal import ( + master_server_post, + get_input_device_config, + get_input_map_hash, + should_submit_debug_info, + ) + + ba.containerwidget( + edit=self._root_widget, transition=self._transition_out + ) # If we're a secondary editor we just go away (we were editing our # parent's settings dict). @@ -726,14 +842,16 @@ class GamepadSettingsWindow(ba.Window): inputhash = get_input_map_hash(self._input) if should_submit_debug_info(): master_server_post( - 'controllerConfig', { + 'controllerConfig', + { 'ua': ba.app.user_agent_string, 'b': ba.app.build_number, 'name': self._name, 'inputMapHash': inputhash, 'config': dst2, - 'v': 2 - }) + 'v': 2, + }, + ) ba.app.config.apply_and_commit() ba.playsound(ba.getsound('gunCocking')) else: @@ -741,21 +859,23 @@ class GamepadSettingsWindow(ba.Window): if self._is_main_menu: from bastd.ui.settings.controls import ControlsSettingsWindow + ba.app.ui.set_main_menu_window( - ControlsSettingsWindow(transition='in_left').get_root_widget()) + ControlsSettingsWindow(transition='in_left').get_root_widget() + ) class AwaitGamepadInputWindow(ba.Window): """Window for capturing a gamepad button press.""" def __init__( - self, - gamepad: ba.InputDevice, - button: str, - callback: Callable[[str, dict[str, Any], AwaitGamepadInputWindow], - Any], - message: ba.Lstr | None = None, - message2: ba.Lstr | None = None): + self, + gamepad: ba.InputDevice, + button: str, + callback: Callable[[str, dict[str, Any], AwaitGamepadInputWindow], Any], + message: ba.Lstr | None = None, + message2: ba.Lstr | None = None, + ): if message is None: print('AwaitGamepadInputWindow message is None!') # Shouldn't get here. @@ -766,41 +886,55 @@ class AwaitGamepadInputWindow(ba.Window): width = 400 height = 150 uiscale = ba.app.ui.uiscale - super().__init__(root_widget=ba.containerwidget( - scale=(2.0 if uiscale is ba.UIScale.SMALL else - 1.9 if uiscale is ba.UIScale.MEDIUM else 1.0), - size=(width, height), - transition='in_scale'), ) - ba.textwidget(parent=self._root_widget, - position=(0, (height - 60) if message2 is None else - (height - 50)), - size=(width, 25), - text=message, - maxwidth=width * 0.9, - h_align='center', - v_align='center') + super().__init__( + root_widget=ba.containerwidget( + scale=( + 2.0 + if uiscale is ba.UIScale.SMALL + else 1.9 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + size=(width, height), + transition='in_scale', + ), + ) + ba.textwidget( + parent=self._root_widget, + position=(0, (height - 60) if message2 is None else (height - 50)), + size=(width, 25), + text=message, + maxwidth=width * 0.9, + h_align='center', + v_align='center', + ) if message2 is not None: - ba.textwidget(parent=self._root_widget, - position=(width * 0.5, height - 60), - size=(0, 0), - text=message2, - maxwidth=width * 0.9, - scale=0.47, - color=(0.7, 1.0, 0.7, 0.6), - h_align='center', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(width * 0.5, height - 60), + size=(0, 0), + text=message2, + maxwidth=width * 0.9, + scale=0.47, + color=(0.7, 1.0, 0.7, 0.6), + h_align='center', + v_align='center', + ) self._counter = 5 - self._count_down_text = ba.textwidget(parent=self._root_widget, - h_align='center', - position=(0, height - 110), - size=(width, 25), - color=(1, 1, 1, 0.3), - text=str(self._counter)) + self._count_down_text = ba.textwidget( + parent=self._root_widget, + h_align='center', + position=(0, height - 110), + size=(width, 25), + color=(1, 1, 1, 0.3), + text=str(self._counter), + ) self._decrement_timer: ba.Timer | None = ba.Timer( 1.0, ba.Call(self._decrement), repeat=True, - timetype=ba.TimeType.REAL) + timetype=ba.TimeType.REAL, + ) ba.internal.capture_gamepad_input(ba.WeakCall(self._event_callback)) def __del__(self) -> None: @@ -820,16 +954,20 @@ class AwaitGamepadInputWindow(ba.Window): assert isinstance(input_device, ba.InputDevice) # Update - we now allow *any* input device of this type. - if (self._input and input_device - and input_device.name == self._input.name): + if ( + self._input + and input_device + and input_device.name == self._input.name + ): self._callback(self._capture_button, event, self) def _decrement(self) -> None: self._counter -= 1 if self._counter >= 1: if self._count_down_text: - ba.textwidget(edit=self._count_down_text, - text=str(self._counter)) + ba.textwidget( + edit=self._count_down_text, text=str(self._counter) + ) else: ba.playsound(ba.getsound('error')) self.die() diff --git a/dist/ba_data/python/bastd/ui/settings/gamepadadvanced.py b/dist/ba_data/python/bastd/ui/settings/gamepadadvanced.py index e97b318..7326588 100644 --- a/dist/ba_data/python/bastd/ui/settings/gamepadadvanced.py +++ b/dist/ba_data/python/bastd/ui/settings/gamepadadvanced.py @@ -18,6 +18,7 @@ class GamepadAdvancedSettingsWindow(ba.Window): def __init__(self, parent_window: gpsui.GamepadSettingsWindow): # pylint: disable=too-many-statements + # pylint: disable=too-many-locals self._parent_window = parent_window app = ba.app @@ -28,62 +29,88 @@ class GamepadAdvancedSettingsWindow(ba.Window): self._x_inset = x_inset = 100 if uiscale is ba.UIScale.SMALL else 0 self._height = 402 if uiscale is ba.UIScale.SMALL else 512 self._textwidgets: dict[str, ba.Widget] = {} - super().__init__(root_widget=ba.containerwidget( - transition='in_scale', - size=(self._width, self._height), - scale=1.06 * (1.85 if uiscale is ba.UIScale.SMALL else - 1.35 if uiscale is ba.UIScale.MEDIUM else 1.0), - stack_offset=(0, -25) if uiscale is ba.UIScale.SMALL else (0, 0), - scale_origin_stack_offset=(parent_window.get_advanced_button(). - get_screen_space_center()))) + advb = parent_window.get_advanced_button() + super().__init__( + root_widget=ba.containerwidget( + transition='in_scale', + size=(self._width, self._height), + scale=1.06 + * ( + 1.85 + if uiscale is ba.UIScale.SMALL + else 1.35 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + stack_offset=(0, -25) + if uiscale is ba.UIScale.SMALL + else (0, 0), + scale_origin_stack_offset=(advb.get_screen_space_center()), + ) + ) - ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, self._height - - (40 if uiscale is ba.UIScale.SMALL else 34)), - size=(0, 0), - text=ba.Lstr(resource=self._r + '.advancedTitleText'), - maxwidth=320, - color=ba.app.ui.title_color, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=( + self._width * 0.5, + self._height - (40 if uiscale is ba.UIScale.SMALL else 34), + ), + size=(0, 0), + text=ba.Lstr(resource=self._r + '.advancedTitleText'), + maxwidth=320, + color=ba.app.ui.title_color, + h_align='center', + v_align='center', + ) back_button = btn = ba.buttonwidget( parent=self._root_widget, autoselect=True, - position=(self._width - (176 + x_inset), self._height - - (60 if uiscale is ba.UIScale.SMALL else 55)), + position=( + self._width - (176 + x_inset), + self._height - (60 if uiscale is ba.UIScale.SMALL else 55), + ), size=(120, 48), text_scale=0.8, label=ba.Lstr(resource='doneText'), - on_activate_call=self._done) - ba.containerwidget(edit=self._root_widget, - start_button=btn, - on_cancel_call=btn.activate) + on_activate_call=self._done, + ) + ba.containerwidget( + edit=self._root_widget, + start_button=btn, + on_cancel_call=btn.activate, + ) self._scroll_width = self._width - (100 + 2 * x_inset) self._scroll_height = self._height - 110 self._sub_width = self._scroll_width - 20 - self._sub_height = (940 if self._parent_window.get_is_secondary() else - 1040) + self._sub_height = ( + 940 if self._parent_window.get_is_secondary() else 1040 + ) if app.vr_mode: self._sub_height += 50 self._scrollwidget = ba.scrollwidget( parent=self._root_widget, - position=((self._width - self._scroll_width) * 0.5, - self._height - 65 - self._scroll_height), + position=( + (self._width - self._scroll_width) * 0.5, + self._height - 65 - self._scroll_height, + ), size=(self._scroll_width, self._scroll_height), claims_left_right=True, claims_tab=True, - selection_loops_to_parent=True) - self._subcontainer = ba.containerwidget(parent=self._scrollwidget, - size=(self._sub_width, - self._sub_height), - background=False, - claims_left_right=True, - claims_tab=True, - selection_loops_to_parent=True) - ba.containerwidget(edit=self._root_widget, - selected_child=self._scrollwidget) + selection_loops_to_parent=True, + ) + self._subcontainer = ba.containerwidget( + parent=self._scrollwidget, + size=(self._sub_width, self._sub_height), + background=False, + claims_left_right=True, + claims_tab=True, + selection_loops_to_parent=True, + ) + ba.containerwidget( + edit=self._root_widget, selected_child=self._scrollwidget + ) h = 30 v = self._sub_height - 10 @@ -101,16 +128,19 @@ class GamepadAdvancedSettingsWindow(ba.Window): textcolor=(0.8, 0.8, 0.8), maxwidth=330, scale=1.0, - on_value_change_call=self._parent_window. - set_unassigned_buttons_run_value, + on_value_change_call=( + self._parent_window.set_unassigned_buttons_run_value + ), autoselect=True, - value=self._parent_window.get_unassigned_buttons_run_value()) + value=self._parent_window.get_unassigned_buttons_run_value(), + ) ba.widget(edit=cb1, up_widget=back_button) v -= 60 capb = self._capture_button( pos=(h2, v), name=ba.Lstr(resource=self._r + '.runButton1Text'), - control='buttonRun1' + self._parent_window.get_ext()) + control='buttonRun1' + self._parent_window.get_ext(), + ) if self._parent_window.get_is_secondary(): for widget in capb: ba.widget(edit=widget, up_widget=back_button) @@ -118,17 +148,19 @@ class GamepadAdvancedSettingsWindow(ba.Window): self._capture_button( pos=(h2, v), name=ba.Lstr(resource=self._r + '.runButton2Text'), - control='buttonRun2' + self._parent_window.get_ext()) - ba.textwidget(parent=self._subcontainer, - position=(self._sub_width * 0.5, v - 24), - size=(0, 0), - text=ba.Lstr(resource=self._r + - '.runTriggerDescriptionText'), - color=(0.7, 1, 0.7, 0.6), - maxwidth=self._sub_width * 0.8, - scale=0.7, - h_align='center', - v_align='center') + control='buttonRun2' + self._parent_window.get_ext(), + ) + ba.textwidget( + parent=self._subcontainer, + position=(self._sub_width * 0.5, v - 24), + size=(0, 0), + text=ba.Lstr(resource=self._r + '.runTriggerDescriptionText'), + color=(0.7, 1, 0.7, 0.6), + maxwidth=self._sub_width * 0.8, + scale=0.7, + h_align='center', + v_align='center', + ) v -= 85 @@ -136,13 +168,15 @@ class GamepadAdvancedSettingsWindow(ba.Window): pos=(h2, v), name=ba.Lstr(resource=self._r + '.runTrigger1Text'), control='triggerRun1' + self._parent_window.get_ext(), - message=ba.Lstr(resource=self._r + '.pressAnyAnalogTriggerText')) + message=ba.Lstr(resource=self._r + '.pressAnyAnalogTriggerText'), + ) v -= 42 self._capture_button( pos=(h2, v), name=ba.Lstr(resource=self._r + '.runTrigger2Text'), control='triggerRun2' + self._parent_window.get_ext(), - message=ba.Lstr(resource=self._r + '.pressAnyAnalogTriggerText')) + message=ba.Lstr(resource=self._r + '.pressAnyAnalogTriggerText'), + ) # in vr mode, allow assigning a reset-view button if app.vr_mode: @@ -150,69 +184,80 @@ class GamepadAdvancedSettingsWindow(ba.Window): self._capture_button( pos=(h2, v), name=ba.Lstr(resource=self._r + '.vrReorientButtonText'), - control='buttonVRReorient' + self._parent_window.get_ext()) + control='buttonVRReorient' + self._parent_window.get_ext(), + ) v -= 60 self._capture_button( pos=(h2, v), name=ba.Lstr(resource=self._r + '.extraStartButtonText'), - control='buttonStart2' + self._parent_window.get_ext()) + control='buttonStart2' + self._parent_window.get_ext(), + ) v -= 60 self._capture_button( pos=(h2, v), name=ba.Lstr(resource=self._r + '.ignoredButton1Text'), - control='buttonIgnored' + self._parent_window.get_ext()) + control='buttonIgnored' + self._parent_window.get_ext(), + ) v -= 42 self._capture_button( pos=(h2, v), name=ba.Lstr(resource=self._r + '.ignoredButton2Text'), - control='buttonIgnored2' + self._parent_window.get_ext()) + control='buttonIgnored2' + self._parent_window.get_ext(), + ) v -= 42 self._capture_button( pos=(h2, v), name=ba.Lstr(resource=self._r + '.ignoredButton3Text'), - control='buttonIgnored3' + self._parent_window.get_ext()) + control='buttonIgnored3' + self._parent_window.get_ext(), + ) v -= 42 self._capture_button( pos=(h2, v), name=ba.Lstr(resource=self._r + '.ignoredButton4Text'), - control='buttonIgnored4' + self._parent_window.get_ext()) - ba.textwidget(parent=self._subcontainer, - position=(self._sub_width * 0.5, v - 14), - size=(0, 0), - text=ba.Lstr(resource=self._r + - '.ignoredButtonDescriptionText'), - color=(0.7, 1, 0.7, 0.6), - scale=0.8, - maxwidth=self._sub_width * 0.8, - h_align='center', - v_align='center') + control='buttonIgnored4' + self._parent_window.get_ext(), + ) + ba.textwidget( + parent=self._subcontainer, + position=(self._sub_width * 0.5, v - 14), + size=(0, 0), + text=ba.Lstr(resource=self._r + '.ignoredButtonDescriptionText'), + color=(0.7, 1, 0.7, 0.6), + scale=0.8, + maxwidth=self._sub_width * 0.8, + h_align='center', + v_align='center', + ) v -= 80 - ba.checkboxwidget(parent=self._subcontainer, - autoselect=True, - position=(h + 50, v), - size=(400, 30), - text=ba.Lstr(resource=self._r + - '.startButtonActivatesDefaultText'), - textcolor=(0.8, 0.8, 0.8), - maxwidth=450, - scale=0.9, - on_value_change_call=self._parent_window. - set_start_button_activates_default_widget_value, - value=self._parent_window. - get_start_button_activates_default_widget_value()) + pwin = self._parent_window + ba.checkboxwidget( + parent=self._subcontainer, + autoselect=True, + position=(h + 50, v), + size=(400, 30), + text=ba.Lstr(resource=self._r + '.startButtonActivatesDefaultText'), + textcolor=(0.8, 0.8, 0.8), + maxwidth=450, + scale=0.9, + on_value_change_call=( + pwin.set_start_button_activates_default_widget_value + ), + value=pwin.get_start_button_activates_default_widget_value(), + ) ba.textwidget( parent=self._subcontainer, position=(self._sub_width * 0.5, v - 12), size=(0, 0), - text=ba.Lstr(resource=self._r + - '.startButtonActivatesDefaultDescriptionText'), + text=ba.Lstr( + resource=self._r + '.startButtonActivatesDefaultDescriptionText' + ), color=(0.7, 1, 0.7, 0.6), maxwidth=self._sub_width * 0.8, scale=0.7, h_align='center', - v_align='center') + v_align='center', + ) v -= 80 ba.checkboxwidget( @@ -225,17 +270,19 @@ class GamepadAdvancedSettingsWindow(ba.Window): maxwidth=450, scale=0.9, on_value_change_call=self._parent_window.set_ui_only_value, - value=self._parent_window.get_ui_only_value()) - ba.textwidget(parent=self._subcontainer, - position=(self._sub_width * 0.5, v - 12), - size=(0, 0), - text=ba.Lstr(resource=self._r + - '.uiOnlyDescriptionText'), - color=(0.7, 1, 0.7, 0.6), - maxwidth=self._sub_width * 0.8, - scale=0.7, - h_align='center', - v_align='center') + value=self._parent_window.get_ui_only_value(), + ) + ba.textwidget( + parent=self._subcontainer, + position=(self._sub_width * 0.5, v - 12), + size=(0, 0), + text=ba.Lstr(resource=self._r + '.uiOnlyDescriptionText'), + color=(0.7, 1, 0.7, 0.6), + maxwidth=self._sub_width * 0.8, + scale=0.7, + h_align='center', + v_align='center', + ) v -= 80 ba.checkboxwidget( @@ -247,19 +294,20 @@ class GamepadAdvancedSettingsWindow(ba.Window): textcolor=(0.8, 0.8, 0.8), maxwidth=450, scale=0.9, - on_value_change_call=self._parent_window. - set_ignore_completely_value, - value=self._parent_window.get_ignore_completely_value()) - ba.textwidget(parent=self._subcontainer, - position=(self._sub_width * 0.5, v - 12), - size=(0, 0), - text=ba.Lstr(resource=self._r + - '.ignoreCompletelyDescriptionText'), - color=(0.7, 1, 0.7, 0.6), - maxwidth=self._sub_width * 0.8, - scale=0.7, - h_align='center', - v_align='center') + on_value_change_call=pwin.set_ignore_completely_value, + value=self._parent_window.get_ignore_completely_value(), + ) + ba.textwidget( + parent=self._subcontainer, + position=(self._sub_width * 0.5, v - 12), + size=(0, 0), + text=ba.Lstr(resource=self._r + '.ignoreCompletelyDescriptionText'), + color=(0.7, 1, 0.7, 0.6), + maxwidth=self._sub_width * 0.8, + scale=0.7, + h_align='center', + v_align='center', + ) v -= 80 @@ -272,20 +320,20 @@ class GamepadAdvancedSettingsWindow(ba.Window): textcolor=(0.8, 0.8, 0.8), maxwidth=450, scale=0.9, - on_value_change_call=self._parent_window. - set_auto_recalibrate_analog_stick_value, - value=self._parent_window.get_auto_recalibrate_analog_stick_value( - )) - ba.textwidget(parent=self._subcontainer, - position=(self._sub_width * 0.5, v - 12), - size=(0, 0), - text=ba.Lstr(resource=self._r + - '.autoRecalibrateDescriptionText'), - color=(0.7, 1, 0.7, 0.6), - maxwidth=self._sub_width * 0.8, - scale=0.7, - h_align='center', - v_align='center') + on_value_change_call=pwin.set_auto_recalibrate_analog_stick_value, + value=self._parent_window.get_auto_recalibrate_analog_stick_value(), + ) + ba.textwidget( + parent=self._subcontainer, + position=(self._sub_width * 0.5, v - 12), + size=(0, 0), + text=ba.Lstr(resource=self._r + '.autoRecalibrateDescriptionText'), + color=(0.7, 1, 0.7, 0.6), + maxwidth=self._sub_width * 0.8, + scale=0.7, + h_align='center', + v_align='center', + ) v -= 80 buttons = self._config_value_editor( @@ -295,20 +343,24 @@ class GamepadAdvancedSettingsWindow(ba.Window): min_val=0, max_val=10.0, increment=0.1, - x_offset=100) + x_offset=100, + ) ba.widget(edit=buttons[0], left_widget=cb1, up_widget=cb1) ba.widget(edit=cb1, right_widget=buttons[0], down_widget=buttons[0]) - ba.textwidget(parent=self._subcontainer, - position=(self._sub_width * 0.5, v - 12), - size=(0, 0), - text=ba.Lstr(resource=self._r + - '.analogStickDeadZoneDescriptionText'), - color=(0.7, 1, 0.7, 0.6), - maxwidth=self._sub_width * 0.8, - scale=0.7, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._subcontainer, + position=(self._sub_width * 0.5, v - 12), + size=(0, 0), + text=ba.Lstr( + resource=self._r + '.analogStickDeadZoneDescriptionText' + ), + color=(0.7, 1, 0.7, 0.6), + maxwidth=self._sub_width * 0.8, + scale=0.7, + h_align='center', + v_align='center', + ) v -= 100 # child joysticks cant have child joysticks.. that's just @@ -321,7 +373,8 @@ class GamepadAdvancedSettingsWindow(ba.Window): position=(40, v), size=(self._sub_width - 80, 50), on_activate_call=self._parent_window.show_secondary_editor, - up_widget=buttons[0]) + up_widget=buttons[0], + ) # set a bigger bottom show-buffer for the widgets we just made # so we can see the text below them when navigating with @@ -330,31 +383,36 @@ class GamepadAdvancedSettingsWindow(ba.Window): ba.widget(edit=child, show_buffer_bottom=30, show_buffer_top=30) def _capture_button( - self, - pos: tuple[float, float], - name: ba.Lstr, - control: str, - message: ba.Lstr | None = None) -> tuple[ba.Widget, ba.Widget]: + self, + pos: tuple[float, float], + name: ba.Lstr, + control: str, + message: ba.Lstr | None = None, + ) -> tuple[ba.Widget, ba.Widget]: if message is None: - message = ba.Lstr(resource=self._parent_window.get_r() + - '.pressAnyButtonText') - btn = ba.buttonwidget(parent=self._subcontainer, - autoselect=True, - position=(pos[0], pos[1]), - label=name, - size=(250, 60), - scale=0.7) - btn2 = ba.buttonwidget(parent=self._subcontainer, - autoselect=True, - position=(pos[0] + 400, pos[1] + 2), - left_widget=btn, - color=(0.45, 0.4, 0.5), - textcolor=(0.65, 0.6, 0.7), - label=ba.Lstr(resource=self._r + '.clearText'), - size=(110, 50), - scale=0.7, - on_activate_call=ba.Call( - self._clear_control, control)) + message = ba.Lstr( + resource=self._parent_window.get_r() + '.pressAnyButtonText' + ) + btn = ba.buttonwidget( + parent=self._subcontainer, + autoselect=True, + position=(pos[0], pos[1]), + label=name, + size=(250, 60), + scale=0.7, + ) + btn2 = ba.buttonwidget( + parent=self._subcontainer, + autoselect=True, + position=(pos[0] + 400, pos[1] + 2), + left_widget=btn, + color=(0.45, 0.4, 0.5), + textcolor=(0.65, 0.6, 0.7), + label=ba.Lstr(resource=self._r + '.clearText'), + size=(110, 50), + scale=0.7, + on_activate_call=ba.Call(self._clear_control, control), + ) ba.widget(edit=btn, right_widget=btn2) # make this in a timer so that it shows up on top of all @@ -362,6 +420,7 @@ class GamepadAdvancedSettingsWindow(ba.Window): def doit() -> None: from bastd.ui.settings import gamepad + txt = ba.textwidget( parent=self._subcontainer, position=(pos[0] + 285, pos[1] + 20), @@ -371,19 +430,26 @@ class GamepadAdvancedSettingsWindow(ba.Window): v_align='center', scale=0.7, text=self._parent_window.get_control_value_name(control), - maxwidth=200) + maxwidth=200, + ) self._textwidgets[control] = txt - ba.buttonwidget(edit=btn, - on_activate_call=ba.Call( - gamepad.AwaitGamepadInputWindow, - self._parent_window.get_input(), control, - self._gamepad_event, message)) + ba.buttonwidget( + edit=btn, + on_activate_call=ba.Call( + gamepad.AwaitGamepadInputWindow, + self._parent_window.get_input(), + control, + self._gamepad_event, + message, + ), + ) ba.timer(0, doit, timetype=ba.TimeType.REAL) return btn, btn2 - def _inc(self, control: str, min_val: float, max_val: float, - inc: float) -> None: + def _inc( + self, control: str, min_val: float, max_val: float, inc: float + ) -> None: val = self._parent_window.get_settings().get(control, 1.0) val = min(max_val, max(min_val, val + inc)) if abs(1.0 - val) < 0.001: @@ -391,32 +457,37 @@ class GamepadAdvancedSettingsWindow(ba.Window): del self._parent_window.get_settings()[control] else: self._parent_window.get_settings()[control] = round(val, 1) - ba.textwidget(edit=self._textwidgets[control], - text=self._parent_window.get_control_value_name(control)) + ba.textwidget( + edit=self._textwidgets[control], + text=self._parent_window.get_control_value_name(control), + ) def _config_value_editor( - self, - name: ba.Lstr, - control: str, - position: tuple[float, float], - min_val: float = 0.0, - max_val: float = 100.0, - increment: float = 1.0, - change_sound: bool = True, - x_offset: float = 0.0, - displayname: ba.Lstr | None = None) -> tuple[ba.Widget, ba.Widget]: + self, + name: ba.Lstr, + control: str, + position: tuple[float, float], + min_val: float = 0.0, + max_val: float = 100.0, + increment: float = 1.0, + change_sound: bool = True, + x_offset: float = 0.0, + displayname: ba.Lstr | None = None, + ) -> tuple[ba.Widget, ba.Widget]: if displayname is None: displayname = name - ba.textwidget(parent=self._subcontainer, - position=position, - size=(100, 30), - text=displayname, - color=(0.8, 0.8, 0.8, 1.0), - h_align='left', - v_align='center', - scale=1.0, - maxwidth=280) + ba.textwidget( + parent=self._subcontainer, + position=position, + size=(100, 30), + text=displayname, + color=(0.8, 0.8, 0.8, 1.0), + h_align='left', + v_align='center', + scale=1.0, + maxwidth=280, + ) self._textwidgets[control] = ba.textwidget( parent=self._subcontainer, position=(246.0 + x_offset, position[1]), @@ -426,51 +497,63 @@ class GamepadAdvancedSettingsWindow(ba.Window): h_align='right', v_align='center', text=self._parent_window.get_control_value_name(control), - padding=2) - btn = ba.buttonwidget(parent=self._subcontainer, - autoselect=True, - position=(330 + x_offset, position[1] + 4), - size=(28, 28), - label='-', - on_activate_call=ba.Call(self._inc, control, - min_val, max_val, - -increment), - repeat=True, - enable_sound=(change_sound is True)) - btn2 = ba.buttonwidget(parent=self._subcontainer, - autoselect=True, - position=(380 + x_offset, position[1] + 4), - size=(28, 28), - label='+', - on_activate_call=ba.Call( - self._inc, control, min_val, max_val, - increment), - repeat=True, - enable_sound=(change_sound is True)) + padding=2, + ) + btn = ba.buttonwidget( + parent=self._subcontainer, + autoselect=True, + position=(330 + x_offset, position[1] + 4), + size=(28, 28), + label='-', + on_activate_call=ba.Call( + self._inc, control, min_val, max_val, -increment + ), + repeat=True, + enable_sound=(change_sound is True), + ) + btn2 = ba.buttonwidget( + parent=self._subcontainer, + autoselect=True, + position=(380 + x_offset, position[1] + 4), + size=(28, 28), + label='+', + on_activate_call=ba.Call( + self._inc, control, min_val, max_val, increment + ), + repeat=True, + enable_sound=(change_sound is True), + ) return btn, btn2 def _clear_control(self, control: str) -> None: if control in self._parent_window.get_settings(): del self._parent_window.get_settings()[control] - ba.textwidget(edit=self._textwidgets[control], - text=self._parent_window.get_control_value_name(control)) + ba.textwidget( + edit=self._textwidgets[control], + text=self._parent_window.get_control_value_name(control), + ) - def _gamepad_event(self, control: str, event: dict[str, Any], - dialog: gpsui.AwaitGamepadInputWindow) -> None: + def _gamepad_event( + self, + control: str, + event: dict[str, Any], + dialog: gpsui.AwaitGamepadInputWindow, + ) -> None: ext = self._parent_window.get_ext() if control in ['triggerRun1' + ext, 'triggerRun2' + ext]: if event['type'] == 'AXISMOTION': # ignore small values or else we might get triggered # by noise if abs(event['value']) > 0.5: - self._parent_window.get_settings()[control] = ( - event['axis']) + self._parent_window.get_settings()[control] = event['axis'] # update the button's text widget if self._textwidgets[control]: ba.textwidget( edit=self._textwidgets[control], text=self._parent_window.get_control_value_name( - control)) + control + ), + ) ba.playsound(ba.getsound('gunCocking')) dialog.die() else: @@ -482,7 +565,9 @@ class GamepadAdvancedSettingsWindow(ba.Window): ba.textwidget( edit=self._textwidgets[control], text=self._parent_window.get_control_value_name( - control)) + control + ), + ) ba.playsound(ba.getsound('gunCocking')) dialog.die() diff --git a/dist/ba_data/python/bastd/ui/settings/gamepadselect.py b/dist/ba_data/python/bastd/ui/settings/gamepadselect.py index 65bdf26..c719985 100644 --- a/dist/ba_data/python/bastd/ui/settings/gamepadselect.py +++ b/dist/ba_data/python/bastd/ui/settings/gamepadselect.py @@ -32,45 +32,63 @@ def gamepad_configure_callback(event: dict[str, Any]) -> None: assert isinstance(inputdevice, ba.InputDevice) if inputdevice.allows_configuring: ba.app.ui.set_main_menu_window( - gamepad.GamepadSettingsWindow(inputdevice).get_root_widget()) + gamepad.GamepadSettingsWindow(inputdevice).get_root_widget() + ) else: width = 700 height = 200 button_width = 100 uiscale = ba.app.ui.uiscale - dlg = (ba.containerwidget( - scale=(1.7 if uiscale is ba.UIScale.SMALL else - 1.4 if uiscale is ba.UIScale.MEDIUM else 1.0), + dlg = ba.containerwidget( + scale=( + 1.7 + if uiscale is ba.UIScale.SMALL + else 1.4 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), size=(width, height), - transition='in_right')) + transition='in_right', + ) ba.app.ui.set_main_menu_window(dlg) device_name = inputdevice.name if device_name == 'iDevice': - msg = ba.Lstr(resource='bsRemoteConfigureInAppText', - subs=[('${REMOTE_APP_NAME}', get_remote_app_name())]) + msg = ba.Lstr( + resource='bsRemoteConfigureInAppText', + subs=[('${REMOTE_APP_NAME}', get_remote_app_name())], + ) else: - msg = ba.Lstr(resource='cantConfigureDeviceText', - subs=[('${DEVICE}', device_name)]) - ba.textwidget(parent=dlg, - position=(0, height - 80), - size=(width, 25), - text=msg, - scale=0.8, - h_align='center', - v_align='top') + msg = ba.Lstr( + resource='cantConfigureDeviceText', + subs=[('${DEVICE}', device_name)], + ) + ba.textwidget( + parent=dlg, + position=(0, height - 80), + size=(width, 25), + text=msg, + scale=0.8, + h_align='center', + v_align='top', + ) def _ok() -> None: from bastd.ui.settings import controls + ba.containerwidget(edit=dlg, transition='out_right') ba.app.ui.set_main_menu_window( controls.ControlsSettingsWindow( - transition='in_left').get_root_widget()) + transition='in_left' + ).get_root_widget() + ) - ba.buttonwidget(parent=dlg, - position=((width - button_width) / 2, 20), - size=(button_width, 60), - label=ba.Lstr(resource='okText'), - on_activate_call=_ok) + ba.buttonwidget( + parent=dlg, + position=((width - button_width) / 2, 20), + size=(button_width, 60), + label=ba.Lstr(resource='okText'), + on_activate_call=_ok, + ) class GamepadSelectWindow(ba.Window): @@ -78,74 +96,97 @@ class GamepadSelectWindow(ba.Window): def __init__(self) -> None: from typing import cast + width = 480 height = 170 spacing = 40 self._r = 'configGamepadSelectWindow' uiscale = ba.app.ui.uiscale - super().__init__(root_widget=ba.containerwidget( - scale=(2.3 if uiscale is ba.UIScale.SMALL else - 1.5 if uiscale is ba.UIScale.MEDIUM else 1.0), - size=(width, height), - transition='in_right', - )) + super().__init__( + root_widget=ba.containerwidget( + scale=( + 2.3 + if uiscale is ba.UIScale.SMALL + else 1.5 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + size=(width, height), + transition='in_right', + ) + ) - btn = ba.buttonwidget(parent=self._root_widget, - position=(20, height - 60), - size=(130, 60), - label=ba.Lstr(resource='backText'), - button_type='back', - scale=0.8, - on_activate_call=self._back) + btn = ba.buttonwidget( + parent=self._root_widget, + position=(20, height - 60), + size=(130, 60), + label=ba.Lstr(resource='backText'), + button_type='back', + scale=0.8, + on_activate_call=self._back, + ) # Let's not have anything selected by default; its misleading looking # for the controller getting configured. - ba.containerwidget(edit=self._root_widget, - cancel_button=btn, - selected_child=cast(ba.Widget, 0)) - ba.textwidget(parent=self._root_widget, - position=(20, height - 50), - size=(width, 25), - text=ba.Lstr(resource=self._r + '.titleText'), - maxwidth=250, - color=ba.app.ui.title_color, - h_align='center', - v_align='center') + ba.containerwidget( + edit=self._root_widget, + cancel_button=btn, + selected_child=cast(ba.Widget, 0), + ) + ba.textwidget( + parent=self._root_widget, + position=(20, height - 50), + size=(width, 25), + text=ba.Lstr(resource=self._r + '.titleText'), + maxwidth=250, + color=ba.app.ui.title_color, + h_align='center', + v_align='center', + ) - ba.buttonwidget(edit=btn, - button_type='backSmall', - size=(60, 60), - label=ba.charstr(ba.SpecialChar.BACK)) + ba.buttonwidget( + edit=btn, + button_type='backSmall', + size=(60, 60), + label=ba.charstr(ba.SpecialChar.BACK), + ) v: float = height - 60 v -= spacing - ba.textwidget(parent=self._root_widget, - position=(15, v), - size=(width - 30, 30), - scale=0.8, - text=ba.Lstr(resource=self._r + '.pressAnyButtonText'), - maxwidth=width * 0.95, - color=ba.app.ui.infotextcolor, - h_align='center', - v_align='top') + ba.textwidget( + parent=self._root_widget, + position=(15, v), + size=(width - 30, 30), + scale=0.8, + text=ba.Lstr(resource=self._r + '.pressAnyButtonText'), + maxwidth=width * 0.95, + color=ba.app.ui.infotextcolor, + h_align='center', + v_align='top', + ) v -= spacing * 1.24 if ba.app.platform == 'android': - ba.textwidget(parent=self._root_widget, - position=(15, v), - size=(width - 30, 30), - scale=0.46, - text=ba.Lstr(resource=self._r + '.androidNoteText'), - maxwidth=width * 0.95, - color=(0.7, 0.9, 0.7, 0.5), - h_align='center', - v_align='top') + ba.textwidget( + parent=self._root_widget, + position=(15, v), + size=(width - 30, 30), + scale=0.46, + text=ba.Lstr(resource=self._r + '.androidNoteText'), + maxwidth=width * 0.95, + color=(0.7, 0.9, 0.7, 0.5), + h_align='center', + v_align='top', + ) ba.internal.capture_gamepad_input(gamepad_configure_callback) def _back(self) -> None: from bastd.ui.settings import controls + ba.internal.release_gamepad_input() ba.containerwidget(edit=self._root_widget, transition='out_right') ba.app.ui.set_main_menu_window( controls.ControlsSettingsWindow( - transition='in_left').get_root_widget()) + transition='in_left' + ).get_root_widget() + ) diff --git a/dist/ba_data/python/bastd/ui/settings/graphics.py b/dist/ba_data/python/bastd/ui/settings/graphics.py index 09eb1f2..a771f6e 100644 --- a/dist/ba_data/python/bastd/ui/settings/graphics.py +++ b/dist/ba_data/python/bastd/ui/settings/graphics.py @@ -16,14 +16,17 @@ if TYPE_CHECKING: class GraphicsSettingsWindow(ba.Window): """Window for graphics settings.""" - def __init__(self, - transition: str = 'in_right', - origin_widget: ba.Widget | None = None): + def __init__( + self, + transition: str = 'in_right', + origin_widget: ba.Widget | None = None, + ): # pylint: disable=too-many-locals # pylint: disable=too-many-branches # pylint: disable=too-many-statements from bastd.ui import popup from bastd.ui.config import ConfigCheckBox, ConfigNumberEdit + # if they provided an origin-widget, scale up from that scale_origin: tuple[float, float] | None if origin_widget is not None: @@ -62,46 +65,63 @@ class GraphicsSettingsWindow(ba.Window): show_resolution = True if app.vr_mode: - show_resolution = (app.platform == 'android' - and app.subplatform == 'cardboard') + show_resolution = ( + app.platform == 'android' and app.subplatform == 'cardboard' + ) uiscale = ba.app.ui.uiscale - base_scale = (2.4 if uiscale is ba.UIScale.SMALL else - 1.5 if uiscale is ba.UIScale.MEDIUM else 1.0) + base_scale = ( + 2.4 + if uiscale is ba.UIScale.SMALL + else 1.5 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ) popup_menu_scale = base_scale * 1.2 v = height - 50 v -= spacing * 1.15 - super().__init__(root_widget=ba.containerwidget( - size=(width, height), - transition=transition, - scale_origin_stack_offset=scale_origin, - scale=base_scale, - stack_offset=(0, -30) if uiscale is ba.UIScale.SMALL else (0, 0))) + super().__init__( + root_widget=ba.containerwidget( + size=(width, height), + transition=transition, + scale_origin_stack_offset=scale_origin, + scale=base_scale, + stack_offset=(0, -30) + if uiscale is ba.UIScale.SMALL + else (0, 0), + ) + ) - btn = ba.buttonwidget(parent=self._root_widget, - position=(35, height - 50), - size=(120, 60), - scale=0.8, - text_scale=1.2, - autoselect=True, - label=ba.Lstr(resource='backText'), - button_type='back', - on_activate_call=self._back) + btn = ba.buttonwidget( + parent=self._root_widget, + position=(35, height - 50), + size=(120, 60), + scale=0.8, + text_scale=1.2, + autoselect=True, + label=ba.Lstr(resource='backText'), + button_type='back', + on_activate_call=self._back, + ) ba.containerwidget(edit=self._root_widget, cancel_button=btn) - ba.textwidget(parent=self._root_widget, - position=(0, height - 44), - size=(width, 25), - text=ba.Lstr(resource=self._r + '.titleText'), - color=ba.app.ui.title_color, - h_align='center', - v_align='top') + ba.textwidget( + parent=self._root_widget, + position=(0, height - 44), + size=(width, 25), + text=ba.Lstr(resource=self._r + '.titleText'), + color=ba.app.ui.title_color, + h_align='center', + v_align='top', + ) - ba.buttonwidget(edit=btn, - button_type='backSmall', - size=(60, 60), - label=ba.charstr(ba.SpecialChar.BACK)) + ba.buttonwidget( + edit=btn, + button_type='backSmall', + size=(60, 60), + label=ba.charstr(ba.SpecialChar.BACK), + ) self._fullscreen_checkbox: ba.Widget | None if self._show_fullscreen: @@ -112,12 +132,20 @@ class GraphicsSettingsWindow(ba.Window): maxwidth=200, size=(300, 30), configkey='Fullscreen', - displayname=ba.Lstr(resource=self._r + - ('.fullScreenCmdText' if app.platform == - 'mac' else '.fullScreenCtrlText'))).widget + displayname=ba.Lstr( + resource=self._r + + ( + '.fullScreenCmdText' + if app.platform == 'mac' + else '.fullScreenCtrlText' + ) + ), + ).widget if not self._have_selected_child: - ba.containerwidget(edit=self._root_widget, - selected_child=self._fullscreen_checkbox) + ba.containerwidget( + edit=self._root_widget, + selected_child=self._fullscreen_checkbox, + ) self._have_selected_child = True v -= fullscreen_spacing else: @@ -134,14 +162,17 @@ class GraphicsSettingsWindow(ba.Window): maxval=2.0, increment=0.1, xoffset=-70, - textscale=0.85) + textscale=0.85, + ) if ba.app.ui.use_toolbars: - ba.widget(edit=gmc.plusbutton, - right_widget=ba.internal.get_special_widget( - 'party_button')) + ba.widget( + edit=gmc.plusbutton, + right_widget=ba.internal.get_special_widget('party_button'), + ) if not self._have_selected_child: - ba.containerwidget(edit=self._root_widget, - selected_child=gmc.minusbutton) + ba.containerwidget( + edit=self._root_widget, selected_child=gmc.minusbutton + ) self._have_selected_child = True v -= gamma_spacing else: @@ -151,15 +182,17 @@ class GraphicsSettingsWindow(ba.Window): self._unselected_color = (0.7, 0.7, 0.7, 1) # quality - ba.textwidget(parent=self._root_widget, - position=(60, v), - size=(160, 25), - text=ba.Lstr(resource=self._r + '.visualsText'), - color=ba.app.ui.heading_color, - scale=0.65, - maxwidth=150, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(60, v), + size=(160, 25), + text=ba.Lstr(resource=self._r + '.visualsText'), + color=ba.app.ui.heading_color, + scale=0.65, + maxwidth=150, + h_align='center', + v_align='center', + ) popup.PopupMenu( parent=self._root_widget, position=(60, v - 50), @@ -167,27 +200,31 @@ class GraphicsSettingsWindow(ba.Window): scale=popup_menu_scale, choices=['Auto', 'Higher', 'High', 'Medium', 'Low'], choices_disabled=['Higher', 'High'] - if ba.internal.get_max_graphics_quality() == 'Medium' else [], + if ba.internal.get_max_graphics_quality() == 'Medium' + else [], choices_display=[ ba.Lstr(resource='autoText'), ba.Lstr(resource=self._r + '.higherText'), ba.Lstr(resource=self._r + '.highText'), ba.Lstr(resource=self._r + '.mediumText'), - ba.Lstr(resource=self._r + '.lowText') + ba.Lstr(resource=self._r + '.lowText'), ], current_choice=ba.app.config.resolve('Graphics Quality'), - on_value_change_call=self._set_quality) + on_value_change_call=self._set_quality, + ) # texture controls - ba.textwidget(parent=self._root_widget, - position=(230, v), - size=(160, 25), - text=ba.Lstr(resource=self._r + '.texturesText'), - color=ba.app.ui.heading_color, - scale=0.65, - maxwidth=150, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(230, v), + size=(160, 25), + text=ba.Lstr(resource=self._r + '.texturesText'), + color=ba.app.ui.heading_color, + scale=0.65, + maxwidth=150, + h_align='center', + v_align='center', + ) textures_popup = popup.PopupMenu( parent=self._root_widget, position=(230, v - 50), @@ -198,29 +235,33 @@ class GraphicsSettingsWindow(ba.Window): ba.Lstr(resource='autoText'), ba.Lstr(resource=self._r + '.highText'), ba.Lstr(resource=self._r + '.mediumText'), - ba.Lstr(resource=self._r + '.lowText') + ba.Lstr(resource=self._r + '.lowText'), ], current_choice=ba.app.config.resolve('Texture Quality'), - on_value_change_call=self._set_textures) + on_value_change_call=self._set_textures, + ) if ba.app.ui.use_toolbars: ba.widget( edit=textures_popup.get_button(), - right_widget=ba.internal.get_special_widget('party_button')) + right_widget=ba.internal.get_special_widget('party_button'), + ) v -= 80 h_offs = 0 if show_resolution: # resolution - ba.textwidget(parent=self._root_widget, - position=(h_offs + 60, v), - size=(160, 25), - text=ba.Lstr(resource=self._r + '.resolutionText'), - color=ba.app.ui.heading_color, - scale=0.65, - maxwidth=150, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(h_offs + 60, v), + size=(160, 25), + text=ba.Lstr(resource=self._r + '.resolutionText'), + color=ba.app.ui.heading_color, + scale=0.65, + maxwidth=150, + h_align='center', + v_align='center', + ) # on standard android we have 'Auto', 'Native', and a few # HD standards @@ -228,9 +269,25 @@ class GraphicsSettingsWindow(ba.Window): # on cardboard/daydream android we have a few # render-target-scale options if app.subplatform == 'cardboard': - current_res_cardboard = (str(min(100, max(10, int(round( - ba.app.config.resolve('GVR Render Target Scale') - * 100.0))))) + '%') # yapf: disable + current_res_cardboard = ( + str( + min( + 100, + max( + 10, + int( + round( + ba.app.config.resolve( + 'GVR Render Target Scale' + ) + * 100.0 + ) + ), + ), + ) + ) + + '%' + ) # yapf: disable popup.PopupMenu( parent=self._root_widget, position=(h_offs + 60, v - 50), @@ -238,14 +295,15 @@ class GraphicsSettingsWindow(ba.Window): scale=popup_menu_scale, choices=['100%', '75%', '50%', '35%'], current_choice=current_res_cardboard, - on_value_change_call=self._set_gvr_render_target_scale) + on_value_change_call=self._set_gvr_render_target_scale, + ) else: native_res = ba.internal.get_display_resolution() assert native_res is not None choices = ['Auto', 'Native'] choices_display = [ ba.Lstr(resource='autoText'), - ba.Lstr(resource='nativeText') + ba.Lstr(resource='nativeText'), ] for res in [1440, 1080, 960, 720, 480]: # nav bar is 72px so lets allow for that in what @@ -255,23 +313,42 @@ class GraphicsSettingsWindow(ba.Window): choices.append(res_str) choices_display.append(ba.Lstr(value=res_str)) current_res_android = ba.app.config.resolve( - 'Resolution (Android)') - popup.PopupMenu(parent=self._root_widget, - position=(h_offs + 60, v - 50), - width=120, - scale=popup_menu_scale, - choices=choices, - choices_display=choices_display, - current_choice=current_res_android, - on_value_change_call=self._set_android_res) + 'Resolution (Android)' + ) + popup.PopupMenu( + parent=self._root_widget, + position=(h_offs + 60, v - 50), + width=120, + scale=popup_menu_scale, + choices=choices, + choices_display=choices_display, + current_choice=current_res_android, + on_value_change_call=self._set_android_res, + ) else: # if we're on a system that doesn't allow setting resolution, # set pixel-scale instead current_res = ba.internal.get_display_resolution() if current_res is None: - current_res2 = (str(min(100, max(10, int(round( - ba.app.config.resolve('Screen Pixel Scale') - * 100.0))))) + '%') # yapf: disable + current_res2 = ( + str( + min( + 100, + max( + 10, + int( + round( + ba.app.config.resolve( + 'Screen Pixel Scale' + ) + * 100.0 + ) + ), + ), + ) + ) + + '%' + ) # yapf: disable popup.PopupMenu( parent=self._root_widget, position=(h_offs + 60, v - 50), @@ -279,22 +356,27 @@ class GraphicsSettingsWindow(ba.Window): scale=popup_menu_scale, choices=['100%', '88%', '75%', '63%', '50%'], current_choice=current_res2, - on_value_change_call=self._set_pixel_scale) + on_value_change_call=self._set_pixel_scale, + ) else: - raise Exception('obsolete path; discrete resolutions' - ' no longer supported') + raise Exception( + 'obsolete path; discrete resolutions' + ' no longer supported' + ) # vsync if show_vsync: - ba.textwidget(parent=self._root_widget, - position=(230, v), - size=(160, 25), - text=ba.Lstr(resource=self._r + '.verticalSyncText'), - color=ba.app.ui.heading_color, - scale=0.65, - maxwidth=150, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(230, v), + size=(160, 25), + text=ba.Lstr(resource=self._r + '.verticalSyncText'), + color=ba.app.ui.heading_color, + scale=0.65, + maxwidth=150, + h_align='center', + v_align='center', + ) popup.PopupMenu( parent=self._root_widget, @@ -305,31 +387,34 @@ class GraphicsSettingsWindow(ba.Window): choices_display=[ ba.Lstr(resource='autoText'), ba.Lstr(resource=self._r + '.alwaysText'), - ba.Lstr(resource=self._r + '.neverText') + ba.Lstr(resource=self._r + '.neverText'), ], current_choice=ba.app.config.resolve('Vertical Sync'), - on_value_change_call=self._set_vsync) + on_value_change_call=self._set_vsync, + ) v -= 90 - fpsc = ConfigCheckBox(parent=self._root_widget, - position=(69, v - 6), - size=(210, 30), - scale=0.86, - configkey='Show FPS', - displayname=ba.Lstr(resource=self._r + - '.showFPSText'), - maxwidth=130) + fpsc = ConfigCheckBox( + parent=self._root_widget, + position=(69, v - 6), + size=(210, 30), + scale=0.86, + configkey='Show FPS', + displayname=ba.Lstr(resource=self._r + '.showFPSText'), + maxwidth=130, + ) # (tv mode doesnt apply to vr) if not ba.app.vr_mode: - tvc = ConfigCheckBox(parent=self._root_widget, - position=(240, v - 6), - size=(210, 30), - scale=0.86, - configkey='TV Border', - displayname=ba.Lstr(resource=self._r + - '.tvBorderText'), - maxwidth=130) + tvc = ConfigCheckBox( + parent=self._root_widget, + position=(240, v - 6), + size=(210, 30), + scale=0.86, + configkey='TV Border', + displayname=ba.Lstr(resource=self._r + '.tvBorderText'), + maxwidth=130, + ) # grumble.. ba.widget(edit=fpsc.widget, right_widget=tvc.widget) try: @@ -342,18 +427,24 @@ class GraphicsSettingsWindow(ba.Window): # make a timer to update our controls in case the config changes # under us - self._update_timer = ba.Timer(0.25, - ba.WeakCall(self._update_controls), - repeat=True, - timetype=ba.TimeType.REAL) + self._update_timer = ba.Timer( + 0.25, + ba.WeakCall(self._update_controls), + repeat=True, + timetype=ba.TimeType.REAL, + ) def _back(self) -> None: from bastd.ui.settings import allsettings - ba.containerwidget(edit=self._root_widget, - transition=self._transition_out) + + ba.containerwidget( + edit=self._root_widget, transition=self._transition_out + ) ba.app.ui.set_main_menu_window( allsettings.AllSettingsWindow( - transition='in_left').get_root_widget()) + transition='in_left' + ).get_root_widget() + ) def _set_quality(self, quality: str) -> None: cfg = ba.app.config @@ -387,5 +478,7 @@ class GraphicsSettingsWindow(ba.Window): def _update_controls(self) -> None: if self._show_fullscreen: - ba.checkboxwidget(edit=self._fullscreen_checkbox, - value=ba.app.config.resolve('Fullscreen')) + ba.checkboxwidget( + edit=self._fullscreen_checkbox, + value=ba.app.config.resolve('Fullscreen'), + ) diff --git a/dist/ba_data/python/bastd/ui/settings/keyboard.py b/dist/ba_data/python/bastd/ui/settings/keyboard.py index 4a2d056..a40b0d0 100644 --- a/dist/ba_data/python/bastd/ui/settings/keyboard.py +++ b/dist/ba_data/python/bastd/ui/settings/keyboard.py @@ -32,79 +32,105 @@ class ConfigKeyboardWindow(ba.Window): self._height = 375 self._spacing = 40 uiscale = ba.app.ui.uiscale - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height), - scale=(1.6 if uiscale is ba.UIScale.SMALL else - 1.3 if uiscale is ba.UIScale.MEDIUM else 1.0), - stack_offset=(0, 5) if uiscale is ba.UIScale.SMALL else (0, 0), - transition=transition)) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height), + scale=( + 1.6 + if uiscale is ba.UIScale.SMALL + else 1.3 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + stack_offset=(0, 5) if uiscale is ba.UIScale.SMALL else (0, 0), + transition=transition, + ) + ) self._rebuild_ui() def _rebuild_ui(self) -> None: from ba.internal import get_device_value + for widget in self._root_widget.get_children(): widget.delete() # Fill our temp config with present values. self._settings: dict[str, int] = {} for button in [ - 'buttonJump', 'buttonPunch', 'buttonBomb', 'buttonPickUp', - 'buttonStart', 'buttonStart2', 'buttonUp', 'buttonDown', - 'buttonLeft', 'buttonRight' + 'buttonJump', + 'buttonPunch', + 'buttonBomb', + 'buttonPickUp', + 'buttonStart', + 'buttonStart2', + 'buttonUp', + 'buttonDown', + 'buttonLeft', + 'buttonRight', ]: self._settings[button] = get_device_value(self._input, button) - cancel_button = ba.buttonwidget(parent=self._root_widget, - autoselect=True, - position=(38, self._height - 85), - size=(170, 60), - label=ba.Lstr(resource='cancelText'), - scale=0.9, - on_activate_call=self._cancel) - save_button = ba.buttonwidget(parent=self._root_widget, - autoselect=True, - position=(self._width - 190, - self._height - 85), - size=(180, 60), - label=ba.Lstr(resource='saveText'), - scale=0.9, - text_scale=0.9, - on_activate_call=self._save) - ba.containerwidget(edit=self._root_widget, - cancel_button=cancel_button, - start_button=save_button) + cancel_button = ba.buttonwidget( + parent=self._root_widget, + autoselect=True, + position=(38, self._height - 85), + size=(170, 60), + label=ba.Lstr(resource='cancelText'), + scale=0.9, + on_activate_call=self._cancel, + ) + save_button = ba.buttonwidget( + parent=self._root_widget, + autoselect=True, + position=(self._width - 190, self._height - 85), + size=(180, 60), + label=ba.Lstr(resource='saveText'), + scale=0.9, + text_scale=0.9, + on_activate_call=self._save, + ) + ba.containerwidget( + edit=self._root_widget, + cancel_button=cancel_button, + start_button=save_button, + ) ba.widget(edit=cancel_button, right_widget=save_button) ba.widget(edit=save_button, left_widget=cancel_button) v = self._height - 74.0 - ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, v + 15), - size=(0, 0), - text=ba.Lstr(resource=self._r + '.configuringText', - subs=[('${DEVICE}', self._displayname)]), - color=ba.app.ui.title_color, - h_align='center', - v_align='center', - maxwidth=270, - scale=0.83) + ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, v + 15), + size=(0, 0), + text=ba.Lstr( + resource=self._r + '.configuringText', + subs=[('${DEVICE}', self._displayname)], + ), + color=ba.app.ui.title_color, + h_align='center', + v_align='center', + maxwidth=270, + scale=0.83, + ) v -= 20 if self._unique_id != '#1': v -= 20 v -= self._spacing - ba.textwidget(parent=self._root_widget, - position=(0, v + 19), - size=(self._width, 50), - text=ba.Lstr(resource=self._r + - '.keyboard2NoteText'), - scale=0.7, - maxwidth=self._width * 0.75, - max_height=110, - color=ba.app.ui.infotextcolor, - h_align='center', - v_align='top') + ba.textwidget( + parent=self._root_widget, + position=(0, v + 19), + size=(self._width, 50), + text=ba.Lstr(resource=self._r + '.keyboard2NoteText'), + scale=0.7, + maxwidth=self._width * 0.75, + max_height=110, + color=ba.app.ui.infotextcolor, + h_align='center', + v_align='top', + ) v -= 40 v -= 10 v -= self._spacing * 2.2 @@ -113,72 +139,96 @@ class ConfigKeyboardWindow(ba.Window): h_offs = 160 dist = 70 d_color = (0.4, 0.4, 0.8) - self._capture_button(pos=(h_offs, v + 0.95 * dist), - color=d_color, - button='buttonUp', - texture=ba.gettexture('upButton'), - scale=1.0) - self._capture_button(pos=(h_offs - 1.2 * dist, v), - color=d_color, - button='buttonLeft', - texture=ba.gettexture('leftButton'), - scale=1.0) - self._capture_button(pos=(h_offs + 1.2 * dist, v), - color=d_color, - button='buttonRight', - texture=ba.gettexture('rightButton'), - scale=1.0) - self._capture_button(pos=(h_offs, v - 0.95 * dist), - color=d_color, - button='buttonDown', - texture=ba.gettexture('downButton'), - scale=1.0) + self._capture_button( + pos=(h_offs, v + 0.95 * dist), + color=d_color, + button='buttonUp', + texture=ba.gettexture('upButton'), + scale=1.0, + ) + self._capture_button( + pos=(h_offs - 1.2 * dist, v), + color=d_color, + button='buttonLeft', + texture=ba.gettexture('leftButton'), + scale=1.0, + ) + self._capture_button( + pos=(h_offs + 1.2 * dist, v), + color=d_color, + button='buttonRight', + texture=ba.gettexture('rightButton'), + scale=1.0, + ) + self._capture_button( + pos=(h_offs, v - 0.95 * dist), + color=d_color, + button='buttonDown', + texture=ba.gettexture('downButton'), + scale=1.0, + ) if self._unique_id == '#2': - self._capture_button(pos=(self._width * 0.5, v + 0.1 * dist), - color=(0.4, 0.4, 0.6), - button='buttonStart', - texture=ba.gettexture('startButton'), - scale=0.8) + self._capture_button( + pos=(self._width * 0.5, v + 0.1 * dist), + color=(0.4, 0.4, 0.6), + button='buttonStart', + texture=ba.gettexture('startButton'), + scale=0.8, + ) h_offs = self._width - 160 - self._capture_button(pos=(h_offs, v + 0.95 * dist), - color=(0.6, 0.4, 0.8), - button='buttonPickUp', - texture=ba.gettexture('buttonPickUp'), - scale=1.0) - self._capture_button(pos=(h_offs - 1.2 * dist, v), - color=(0.7, 0.5, 0.1), - button='buttonPunch', - texture=ba.gettexture('buttonPunch'), - scale=1.0) - self._capture_button(pos=(h_offs + 1.2 * dist, v), - color=(0.5, 0.2, 0.1), - button='buttonBomb', - texture=ba.gettexture('buttonBomb'), - scale=1.0) - self._capture_button(pos=(h_offs, v - 0.95 * dist), - color=(0.2, 0.5, 0.2), - button='buttonJump', - texture=ba.gettexture('buttonJump'), - scale=1.0) + self._capture_button( + pos=(h_offs, v + 0.95 * dist), + color=(0.6, 0.4, 0.8), + button='buttonPickUp', + texture=ba.gettexture('buttonPickUp'), + scale=1.0, + ) + self._capture_button( + pos=(h_offs - 1.2 * dist, v), + color=(0.7, 0.5, 0.1), + button='buttonPunch', + texture=ba.gettexture('buttonPunch'), + scale=1.0, + ) + self._capture_button( + pos=(h_offs + 1.2 * dist, v), + color=(0.5, 0.2, 0.1), + button='buttonBomb', + texture=ba.gettexture('buttonBomb'), + scale=1.0, + ) + self._capture_button( + pos=(h_offs, v - 0.95 * dist), + color=(0.2, 0.5, 0.2), + button='buttonJump', + texture=ba.gettexture('buttonJump'), + scale=1.0, + ) - def _capture_button(self, - pos: tuple[float, float], - color: tuple[float, float, float], - texture: ba.Texture, - button: str, - scale: float = 1.0) -> None: + def _capture_button( + self, + pos: tuple[float, float], + color: tuple[float, float, float], + texture: ba.Texture, + button: str, + scale: float = 1.0, + ) -> None: base_size = 79 - btn = ba.buttonwidget(parent=self._root_widget, - autoselect=True, - position=(pos[0] - base_size * 0.5 * scale, - pos[1] - base_size * 0.5 * scale), - size=(base_size * scale, base_size * scale), - texture=texture, - label='', - color=color) + btn = ba.buttonwidget( + parent=self._root_widget, + autoselect=True, + position=( + pos[0] - base_size * 0.5 * scale, + pos[1] - base_size * 0.5 * scale, + ), + size=(base_size * scale, base_size * scale), + texture=texture, + label='', + color=color, + ) # Do this deferred so it shows up on top of other buttons. (ew.) def doit() -> None: @@ -186,35 +236,42 @@ class ConfigKeyboardWindow(ba.Window): return uiscale = 0.66 * scale * 2.0 maxwidth = 76.0 * scale - txt = ba.textwidget(parent=self._root_widget, - position=(pos[0] + 0.0 * scale, - pos[1] - (57.0 - 18.0) * scale), - color=(1, 1, 1, 0.3), - size=(0, 0), - h_align='center', - v_align='top', - scale=uiscale, - maxwidth=maxwidth, - text=self._input.get_button_name( - self._settings[button])) - ba.buttonwidget(edit=btn, - autoselect=True, - on_activate_call=ba.Call(AwaitKeyboardInputWindow, - button, txt, - self._settings)) + txt = ba.textwidget( + parent=self._root_widget, + position=(pos[0] + 0.0 * scale, pos[1] - (57.0 - 18.0) * scale), + color=(1, 1, 1, 0.3), + size=(0, 0), + h_align='center', + v_align='top', + scale=uiscale, + maxwidth=maxwidth, + text=self._input.get_button_name(self._settings[button]), + ) + ba.buttonwidget( + edit=btn, + autoselect=True, + on_activate_call=ba.Call( + AwaitKeyboardInputWindow, button, txt, self._settings + ), + ) ba.pushcall(doit) def _cancel(self) -> None: from bastd.ui.settings.controls import ControlsSettingsWindow + ba.containerwidget(edit=self._root_widget, transition='out_right') ba.app.ui.set_main_menu_window( - ControlsSettingsWindow(transition='in_left').get_root_widget()) + ControlsSettingsWindow(transition='in_left').get_root_widget() + ) def _save(self) -> None: from bastd.ui.settings.controls import ControlsSettingsWindow - from ba.internal import (get_input_device_config, - should_submit_debug_info, master_server_post) + from ba.internal import ( + get_input_device_config, + should_submit_debug_info, + master_server_post, + ) ba.containerwidget(edit=self._root_widget, transition='out_right') ba.playsound(ba.getsound('gunCocking')) @@ -236,16 +293,19 @@ class ConfigKeyboardWindow(ba.Window): # more defaults in the future. if should_submit_debug_info(): master_server_post( - 'controllerConfig', { + 'controllerConfig', + { 'ua': ba.app.user_agent_string, 'name': self._name, 'b': ba.app.build_number, 'config': dst2, - 'v': 2 - }) + 'v': 2, + }, + ) ba.app.config.apply_and_commit() ba.app.ui.set_main_menu_window( - ControlsSettingsWindow(transition='in_left').get_root_widget()) + ControlsSettingsWindow(transition='in_left').get_root_widget() + ) class AwaitKeyboardInputWindow(ba.Window): @@ -260,27 +320,40 @@ class AwaitKeyboardInputWindow(ba.Window): width = 400 height = 150 uiscale = ba.app.ui.uiscale - super().__init__(root_widget=ba.containerwidget( - size=(width, height), - transition='in_right', - scale=(2.0 if uiscale is ba.UIScale.SMALL else - 1.5 if uiscale is ba.UIScale.MEDIUM else 1.0))) - ba.textwidget(parent=self._root_widget, - position=(0, height - 60), - size=(width, 25), - text=ba.Lstr(resource='pressAnyKeyText'), - h_align='center', - v_align='top') + super().__init__( + root_widget=ba.containerwidget( + size=(width, height), + transition='in_right', + scale=( + 2.0 + if uiscale is ba.UIScale.SMALL + else 1.5 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + ) + ) + ba.textwidget( + parent=self._root_widget, + position=(0, height - 60), + size=(width, 25), + text=ba.Lstr(resource='pressAnyKeyText'), + h_align='center', + v_align='top', + ) self._counter = 5 - self._count_down_text = ba.textwidget(parent=self._root_widget, - h_align='center', - position=(0, height - 110), - size=(width, 25), - color=(1, 1, 1, 0.3), - text=str(self._counter)) + self._count_down_text = ba.textwidget( + parent=self._root_widget, + h_align='center', + position=(0, height - 110), + size=(width, 25), + color=(1, 1, 1, 0.3), + text=str(self._counter), + ) self._decrement_timer: ba.Timer | None = ba.Timer( - 1.0, self._decrement, repeat=True, timetype=ba.TimeType.REAL) + 1.0, self._decrement, repeat=True, timetype=ba.TimeType.REAL + ) ba.internal.capture_keyboard_input(ba.WeakCall(self._button_callback)) def __del__(self) -> None: diff --git a/dist/ba_data/python/bastd/ui/settings/nettesting.py b/dist/ba_data/python/bastd/ui/settings/nettesting.py index 9ea4c9b..c50e415 100644 --- a/dist/ba_data/python/bastd/ui/settings/nettesting.py +++ b/dist/ba_data/python/bastd/ui/settings/nettesting.py @@ -31,28 +31,39 @@ class NetTestingWindow(ba.Window): self._height = 500 self._printed_lines: list[str] = [] uiscale = ba.app.ui.uiscale - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height), - scale=(1.56 if uiscale is ba.UIScale.SMALL else - 1.2 if uiscale is ba.UIScale.MEDIUM else 0.8), - stack_offset=(0.0, -7 if uiscale is ba.UIScale.SMALL else 0.0), - transition=transition)) - self._done_button = ba.buttonwidget(parent=self._root_widget, - position=(40, self._height - 77), - size=(120, 60), - scale=0.8, - autoselect=True, - label=ba.Lstr(resource='doneText'), - on_activate_call=self._done) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height), + scale=( + 1.56 + if uiscale is ba.UIScale.SMALL + else 1.2 + if uiscale is ba.UIScale.MEDIUM + else 0.8 + ), + stack_offset=(0.0, -7 if uiscale is ba.UIScale.SMALL else 0.0), + transition=transition, + ) + ) + self._done_button = ba.buttonwidget( + parent=self._root_widget, + position=(40, self._height - 77), + size=(120, 60), + scale=0.8, + autoselect=True, + label=ba.Lstr(resource='doneText'), + on_activate_call=self._done, + ) - self._copy_button = ba.buttonwidget(parent=self._root_widget, - position=(self._width - 200, - self._height - 77), - size=(100, 60), - scale=0.8, - autoselect=True, - label=ba.Lstr(resource='copyText'), - on_activate_call=self._copy) + self._copy_button = ba.buttonwidget( + parent=self._root_widget, + position=(self._width - 200, self._height - 77), + size=(100, 60), + scale=0.8, + autoselect=True, + label=ba.Lstr(resource='copyText'), + on_activate_call=self._copy, + ) self._settings_button = ba.buttonwidget( parent=self._root_widget, @@ -61,7 +72,8 @@ class NetTestingWindow(ba.Window): scale=0.8, autoselect=True, label=ba.Lstr(value='...'), - on_activate_call=self._show_val_testing) + on_activate_call=self._show_val_testing, + ) twidth = self._width - 450 ba.textwidget( @@ -72,18 +84,21 @@ class NetTestingWindow(ba.Window): color=(0.8, 0.8, 0.8, 1.0), h_align='center', v_align='center', - maxwidth=twidth) + maxwidth=twidth, + ) - self._scroll = ba.scrollwidget(parent=self._root_widget, - position=(50, 50), - size=(self._width - 100, - self._height - 140), - capture_arrows=True, - autoselect=True) + self._scroll = ba.scrollwidget( + parent=self._root_widget, + position=(50, 50), + size=(self._width - 100, self._height - 140), + capture_arrows=True, + autoselect=True, + ) self._rows = ba.columnwidget(parent=self._scroll) - ba.containerwidget(edit=self._root_widget, - cancel_button=self._done_button) + ba.containerwidget( + edit=self._root_widget, cancel_button=self._done_button + ) # Now kick off the tests. # Pass a weak-ref to this window so we don't keep it alive @@ -97,20 +112,23 @@ class NetTestingWindow(ba.Window): def print(self, text: str, color: tuple[float, float, float]) -> None: """Print text to our console thingie.""" for line in text.splitlines(): - txt = ba.textwidget(parent=self._rows, - color=color, - text=line, - scale=0.75, - flatness=1.0, - shadow=0.0, - size=(0, 20)) + txt = ba.textwidget( + parent=self._rows, + color=color, + text=line, + scale=0.75, + flatness=1.0, + shadow=0.0, + size=(0, 20), + ) ba.containerwidget(edit=self._rows, visible_child=txt) self._printed_lines.append(line) def _copy(self) -> None: if not ba.clipboard_is_supported(): - ba.screenmessage('Clipboard not supported on this platform.', - color=(1, 0, 0)) + ba.screenmessage( + 'Clipboard not supported on this platform.', color=(1, 0, 0) + ) return ba.clipboard_set_text('\n'.join(self._printed_lines)) ba.screenmessage(f'{len(self._printed_lines)} lines copied.') @@ -122,8 +140,10 @@ class NetTestingWindow(ba.Window): def _done(self) -> None: # pylint: disable=cyclic-import from bastd.ui.settings.advanced import AdvancedSettingsWindow + ba.app.ui.set_main_menu_window( - AdvancedSettingsWindow(transition='in_left').get_root_widget()) + AdvancedSettingsWindow(transition='in_left').get_root_widget() + ) ba.containerwidget(edit=self._root_widget, transition='out_right') @@ -136,9 +156,9 @@ def _run_diagnostics(weakwin: weakref.ref[NetTestingWindow]) -> None: # We're running in a background thread but UI stuff needs to run # in the logic thread; give ourself a way to pass stuff to it. - def _print(text: str, - color: tuple[float, float, float] | None = None) -> None: - + def _print( + text: str, color: tuple[float, float, float] | None = None + ) -> None: def _print_in_logic_thread() -> None: win = weakwin() if win is not None: @@ -146,26 +166,34 @@ def _run_diagnostics(weakwin: weakref.ref[NetTestingWindow]) -> None: ba.pushcall(_print_in_logic_thread, from_other_thread=True) - def _print_test_results(call: Callable[[], Any]) -> None: - """Run the provided call; return success/fail text & color.""" + def _print_test_results(call: Callable[[], Any]) -> bool: + """Run the provided call, print result, & return success.""" starttime = time.monotonic() try: call() duration = time.monotonic() - starttime _print(f'Succeeded in {duration:.2f}s.', color=(0, 1, 0)) + return True except Exception as exc: import traceback + duration = time.monotonic() - starttime - msg = (str(exc) - if isinstance(exc, CleanError) else traceback.format_exc()) + msg = ( + str(exc) + if isinstance(exc, CleanError) + else traceback.format_exc() + ) _print(msg, color=(1.0, 1.0, 0.3)) _print(f'Failed in {duration:.2f}s.', color=(1, 0, 0)) have_error[0] = True + return False try: - _print(f'Running network diagnostics...\n' - f'ua: {ba.app.user_agent_string}\n' - f'time: {utc_now()}.') + _print( + f'Running network diagnostics...\n' + f'ua: {ba.app.user_agent_string}\n' + f'time: {utc_now()}.' + ) if bool(False): _print('\nRunning dummy success test...') @@ -177,14 +205,26 @@ def _run_diagnostics(weakwin: weakref.ref[NetTestingWindow]) -> None: # V1 ping baseaddr = ba.internal.get_master_server_address(source=0, version=1) _print(f'\nContacting V1 master-server src0 ({baseaddr})...') - _print_test_results(lambda: _test_fetch(baseaddr)) + v1worked = _print_test_results(lambda: _test_fetch(baseaddr)) - # V1 alternate ping - baseaddr = ba.internal.get_master_server_address(source=1, version=1) - _print(f'\nContacting V1 master-server src1 ({baseaddr})...') - _print_test_results(lambda: _test_fetch(baseaddr)) + # V1 alternate ping (only if primary fails since this often fails). + if v1worked: + _print('\nSkipping V1 master-server src1 test since src0 worked.') + else: + baseaddr = ba.internal.get_master_server_address( + source=1, version=1 + ) + _print(f'\nContacting V1 master-server src1 ({baseaddr})...') + _print_test_results(lambda: _test_fetch(baseaddr)) - _print(f'\nV1-test-log: {ba.app.net.v1_test_log}') + if 'none succeeded' in ba.app.net.v1_test_log: + _print( + f'\nV1-test-log failed: {ba.app.net.v1_test_log}', + color=(1, 0, 0), + ) + have_error[0] = True + else: + _print(f'\nV1-test-log ok: {ba.app.net.v1_test_log}') for srcid, result in sorted(ba.app.net.v1_ctest_results.items()): _print(f'\nV1 src{srcid} result: {result}') @@ -206,8 +246,11 @@ def _run_diagnostics(weakwin: weakref.ref[NetTestingWindow]) -> None: # Get V2 nearby zone with ba.app.net.zone_pings_lock: zone_pings = copy.deepcopy(ba.app.net.zone_pings) - nearest_zone = (None if not zone_pings else sorted( - zone_pings.items(), key=lambda i: i[1])[0]) + nearest_zone = ( + None + if not zone_pings + else sorted(zone_pings.items(), key=lambda i: i[1])[0] + ) if nearest_zone is not None: nearstr = f'{nearest_zone[0]}: {nearest_zone[1]:.0f}ms' @@ -220,18 +263,24 @@ def _run_diagnostics(weakwin: weakref.ref[NetTestingWindow]) -> None: _print_test_results(_test_v2_cloud_message) if have_error[0]: - _print('\nDiagnostics complete. Some diagnostics failed.', - color=(10, 0, 0)) + _print( + '\nDiagnostics complete. Some diagnostics failed.', + color=(10, 0, 0), + ) else: - _print('\nDiagnostics complete. Everything looks good!', - color=(0, 1, 0)) + _print( + '\nDiagnostics complete. Everything looks good!', + color=(0, 1, 0), + ) except Exception: import traceback + _print( f'An unexpected error occurred during testing;' f' please report this.\n' f'{traceback.format_exc()}', - color=(1, 0, 0)) + color=(1, 0, 0), + ) def _dummy_success() -> None: @@ -278,7 +327,8 @@ def _test_v1_transaction() -> None: time.sleep(0.01) if time.monotonic() - starttime > MAX_TEST_SECONDS: raise RuntimeError( - f'test timed out after {MAX_TEST_SECONDS} seconds') + f'test timed out after {MAX_TEST_SECONDS} seconds' + ) # If we got left a string, its an error. if isinstance(results[0], str): @@ -319,8 +369,10 @@ def _test_v2_cloud_message() -> None: break time.sleep(0.01) if time.monotonic() - wait_start_time > MAX_TEST_SECONDS: - raise RuntimeError(f'Timeout ({MAX_TEST_SECONDS} seconds)' - f' waiting for cloud message response') + raise RuntimeError( + f'Timeout ({MAX_TEST_SECONDS} seconds)' + f' waiting for cloud message response' + ) if results.errstr is not None: raise RuntimeError(results.errstr) @@ -328,28 +380,34 @@ def _test_v2_cloud_message() -> None: def _test_v2_time() -> None: offset = ba.app.net.server_time_offset_hours if offset is None: - raise RuntimeError('no time offset found;' - ' perhaps unable to communicate with v2 server?') + raise RuntimeError( + 'no time offset found;' + ' perhaps unable to communicate with v2 server?' + ) if abs(offset) >= 2.0: raise CleanError( f'Your device time is off from world time by {offset:.1f} hours.\n' 'This may cause network operations to fail due to your device\n' ' incorrectly treating SSL certificates as not-yet-valid, etc.\n' - 'Check your device time and time-zone settings to fix this.\n') + 'Check your device time and time-zone settings to fix this.\n' + ) def _test_fetch(baseaddr: str) -> None: # pylint: disable=consider-using-with import urllib.request + response = urllib.request.urlopen( - urllib.request.Request(f'{baseaddr}/ping', None, - {'User-Agent': ba.app.user_agent_string}), + urllib.request.Request( + f'{baseaddr}/ping', None, {'User-Agent': ba.app.user_agent_string} + ), context=ba.app.net.sslcontext, timeout=MAX_TEST_SECONDS, ) if response.getcode() != 200: raise RuntimeError( - f'Got unexpected response code {response.getcode()}.') + f'Got unexpected response code {response.getcode()}.' + ) data = response.read() if data != b'pong': raise RuntimeError('Got unexpected response data.') @@ -369,29 +427,22 @@ class NetValTestingWindow(TestingWindow): def __init__(self, transition: str = 'in_right'): entries = [ - { - 'name': 'bufferTime', - 'label': 'Buffer Time', - 'increment': 1.0 - }, + {'name': 'bufferTime', 'label': 'Buffer Time', 'increment': 1.0}, { 'name': 'delaySampling', 'label': 'Delay Sampling', - 'increment': 1.0 + 'increment': 1.0, }, { 'name': 'dynamicsSyncTime', 'label': 'Dynamics Sync Time', - 'increment': 10 - }, - { - 'name': 'showNetInfo', - 'label': 'Show Net Info', - 'increment': 1 + 'increment': 10, }, + {'name': 'showNetInfo', 'label': 'Show Net Info', 'increment': 1}, ] super().__init__( title=ba.Lstr(resource='settingsWindowAdvanced.netTestingText'), entries=entries, transition=transition, - back_call=lambda: NetTestingWindow(transition='in_left')) + back_call=lambda: NetTestingWindow(transition='in_left'), + ) diff --git a/dist/ba_data/python/bastd/ui/settings/plugins.py b/dist/ba_data/python/bastd/ui/settings/plugins.py index c90b73d..a819755 100644 --- a/dist/ba_data/python/bastd/ui/settings/plugins.py +++ b/dist/ba_data/python/bastd/ui/settings/plugins.py @@ -15,10 +15,13 @@ if TYPE_CHECKING: class PluginSettingsWindow(ba.Window): """Window for configuring plugins.""" - def __init__(self, - transition: str = 'in_right', - origin_widget: ba.Widget | None = None): + def __init__( + self, + transition: str = 'in_right', + origin_widget: ba.Widget | None = None, + ): # pylint: disable=too-many-locals + # pylint: disable=too-many-statements app = ba.app # If they provided an origin-widget, scale up from that. @@ -34,17 +37,32 @@ class PluginSettingsWindow(ba.Window): uiscale = ba.app.ui.uiscale self._width = 870.0 if uiscale is ba.UIScale.SMALL else 670.0 x_inset = 100 if uiscale is ba.UIScale.SMALL else 0 - self._height = (390.0 if uiscale is ba.UIScale.SMALL else - 450.0 if uiscale is ba.UIScale.MEDIUM else 520.0) + self._height = ( + 390.0 + if uiscale is ba.UIScale.SMALL + else 450.0 + if uiscale is ba.UIScale.MEDIUM + else 520.0 + ) top_extra = 10 if uiscale is ba.UIScale.SMALL else 0 - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height + top_extra), - transition=transition, - toolbar_visibility='menu_minimal', - scale_origin_stack_offset=scale_origin, - scale=(2.06 if uiscale is ba.UIScale.SMALL else - 1.4 if uiscale is ba.UIScale.MEDIUM else 1.0), - stack_offset=(0, -25) if uiscale is ba.UIScale.SMALL else (0, 0))) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height + top_extra), + transition=transition, + toolbar_visibility='menu_minimal', + scale_origin_stack_offset=scale_origin, + scale=( + 2.06 + if uiscale is ba.UIScale.SMALL + else 1.4 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + stack_offset=(0, -25) + if uiscale is ba.UIScale.SMALL + else (0, 0), + ) + ) self._scroll_width = self._width - (100 + 2 * x_inset) self._scroll_height = self._height - 115.0 @@ -52,8 +70,9 @@ class PluginSettingsWindow(ba.Window): self._sub_height = 724.0 if app.ui.use_toolbars and uiscale is ba.UIScale.SMALL: - ba.containerwidget(edit=self._root_widget, - on_cancel_call=self._do_back) + ba.containerwidget( + edit=self._root_widget, on_cancel_call=self._do_back + ) self._back_button = None else: self._back_button = ba.buttonwidget( @@ -64,76 +83,129 @@ class PluginSettingsWindow(ba.Window): autoselect=True, label=ba.Lstr(resource='backText'), button_type='back', - on_activate_call=self._do_back) - ba.containerwidget(edit=self._root_widget, - cancel_button=self._back_button) + on_activate_call=self._do_back, + ) + ba.containerwidget( + edit=self._root_widget, cancel_button=self._back_button + ) - self._title_text = ba.textwidget(parent=self._root_widget, - position=(0, self._height - 52), - size=(self._width, 25), - text=ba.Lstr(resource='pluginsText'), - color=app.ui.title_color, - h_align='center', - v_align='top') + self._title_text = ba.textwidget( + parent=self._root_widget, + position=(0, self._height - 52), + size=(self._width, 25), + text=ba.Lstr(resource='pluginsText'), + color=app.ui.title_color, + h_align='center', + v_align='top', + ) if self._back_button is not None: - ba.buttonwidget(edit=self._back_button, - button_type='backSmall', - size=(60, 60), - label=ba.charstr(ba.SpecialChar.BACK)) + ba.buttonwidget( + edit=self._back_button, + button_type='backSmall', + size=(60, 60), + label=ba.charstr(ba.SpecialChar.BACK), + ) - self._scrollwidget = ba.scrollwidget(parent=self._root_widget, - position=(50 + x_inset, 50), - simple_culling_v=20.0, - highlight=False, - size=(self._scroll_width, - self._scroll_height), - selection_loops_to_parent=True) + self._scrollwidget = ba.scrollwidget( + parent=self._root_widget, + position=(50 + x_inset, 50), + simple_culling_v=20.0, + highlight=False, + size=(self._scroll_width, self._scroll_height), + selection_loops_to_parent=True, + claims_left_right=True, + ) ba.widget(edit=self._scrollwidget, right_widget=self._scrollwidget) - self._subcontainer = ba.columnwidget(parent=self._scrollwidget, - selection_loops_to_parent=True) if ba.app.meta.scanresults is None: - ba.screenmessage('Still scanning plugins; please try again.', - color=(1, 0, 0)) + ba.screenmessage( + 'Still scanning plugins; please try again.', color=(1, 0, 0) + ) ba.playsound(ba.getsound('error')) pluglist = ba.app.plugins.potential_plugins plugstates: dict[str, dict] = ba.app.config.setdefault('Plugins', {}) assert isinstance(plugstates, dict) + + plug_line_height = 50 + sub_width = self._scroll_width + sub_height = len(pluglist) * plug_line_height + self._subcontainer = ba.containerwidget( + parent=self._scrollwidget, + size=(sub_width, sub_height), + background=False, + ) + for i, availplug in enumerate(pluglist): - active = availplug.class_path in ba.app.plugins.active_plugins + plugin = ba.app.plugins.active_plugins.get(availplug.class_path) + active = plugin is not None plugstate = plugstates.setdefault(availplug.class_path, {}) checked = plugstate.get('enabled', False) assert isinstance(checked, bool) + + item_y = sub_height - (i + 1) * plug_line_height check = ba.checkboxwidget( parent=self._subcontainer, text=availplug.display_name, + autoselect=True, value=checked, - maxwidth=self._scroll_width - 100, + maxwidth=self._scroll_width - 200, + position=(10, item_y), size=(self._scroll_width - 40, 50), - on_value_change_call=ba.Call(self._check_value_changed, - availplug), - textcolor=((0.8, 0.3, 0.3) if not availplug.available else - (0, 1, 0) if active else (0.6, 0.6, 0.6))) + on_value_change_call=ba.Call( + self._check_value_changed, availplug + ), + textcolor=( + (0.8, 0.3, 0.3) + if not availplug.available + else (0, 1, 0) + if active + else (0.6, 0.6, 0.6) + ), + ) + if plugin is not None and plugin.has_settings_ui(): + button = ba.buttonwidget( + parent=self._subcontainer, + label=ba.Lstr(resource='mainMenu.settingsText'), + autoselect=True, + size=(100, 40), + position=(sub_width - 130, item_y + 6), + ) + ba.buttonwidget( + edit=button, + on_activate_call=ba.Call(plugin.show_settings_ui, button), + ) + else: + button = None + + # Allow getting back to back button. + if i == 0: + ba.widget( + edit=check, + up_widget=self._back_button, + left_widget=self._back_button, + ) + if button is not None: + ba.widget(edit=button, up_widget=self._back_button) # Make sure we scroll all the way to the end when using # keyboard/button nav. ba.widget(edit=check, show_buffer_top=40, show_buffer_bottom=40) - # Keep last from looping to back button when down is pressed. - if i == len(pluglist) - 1: - ba.widget(edit=check, down_widget=check) - ba.containerwidget(edit=self._root_widget, - selected_child=self._scrollwidget) + ba.containerwidget( + edit=self._root_widget, selected_child=self._scrollwidget + ) self._restore_state() - def _check_value_changed(self, plug: ba.PotentialPlugin, - value: bool) -> None: + def _check_value_changed( + self, plug: ba.PotentialPlugin, value: bool + ) -> None: ba.screenmessage( ba.Lstr(resource='settingsWindowAdvanced.mustRestartText'), - color=(1.0, 0.5, 0.0)) + color=(1.0, 0.5, 0.0), + ) plugstates: dict[str, dict] = ba.app.config.setdefault('Plugins', {}) assert isinstance(plugstates, dict) plugstate = plugstates.setdefault(plug.class_path, {}) @@ -149,8 +221,11 @@ class PluginSettingsWindow(ba.Window): def _do_back(self) -> None: # pylint: disable=cyclic-import from bastd.ui.settings.advanced import AdvancedSettingsWindow + self._save_state() - ba.containerwidget(edit=self._root_widget, - transition=self._transition_out) + ba.containerwidget( + edit=self._root_widget, transition=self._transition_out + ) ba.app.ui.set_main_menu_window( - AdvancedSettingsWindow(transition='in_left').get_root_widget()) + AdvancedSettingsWindow(transition='in_left').get_root_widget() + ) diff --git a/dist/ba_data/python/bastd/ui/settings/remoteapp.py b/dist/ba_data/python/bastd/ui/settings/remoteapp.py index dd81689..c0e8b2e 100644 --- a/dist/ba_data/python/bastd/ui/settings/remoteapp.py +++ b/dist/ba_data/python/bastd/ui/settings/remoteapp.py @@ -12,87 +12,112 @@ class RemoteAppSettingsWindow(ba.Window): def __init__(self) -> None: from ba.internal import get_remote_app_name + self._r = 'connectMobileDevicesWindow' width = 700 height = 390 spacing = 40 uiscale = ba.app.ui.uiscale - super().__init__(root_widget=ba.containerwidget( - size=(width, height), - transition='in_right', - scale=(1.85 if uiscale is ba.UIScale.SMALL else - 1.3 if uiscale is ba.UIScale.MEDIUM else 1.0), - stack_offset=(-10, 0) if uiscale is ba.UIScale.SMALL else (0, 0))) - btn = ba.buttonwidget(parent=self._root_widget, - position=(40, height - 67), - size=(140, 65), - scale=0.8, - label=ba.Lstr(resource='backText'), - button_type='back', - text_scale=1.1, - autoselect=True, - on_activate_call=self._back) + super().__init__( + root_widget=ba.containerwidget( + size=(width, height), + transition='in_right', + scale=( + 1.85 + if uiscale is ba.UIScale.SMALL + else 1.3 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + stack_offset=(-10, 0) + if uiscale is ba.UIScale.SMALL + else (0, 0), + ) + ) + btn = ba.buttonwidget( + parent=self._root_widget, + position=(40, height - 67), + size=(140, 65), + scale=0.8, + label=ba.Lstr(resource='backText'), + button_type='back', + text_scale=1.1, + autoselect=True, + on_activate_call=self._back, + ) ba.containerwidget(edit=self._root_widget, cancel_button=btn) - ba.textwidget(parent=self._root_widget, - position=(width * 0.5, height - 42), - size=(0, 0), - text=ba.Lstr(resource=self._r + '.titleText'), - maxwidth=370, - color=ba.app.ui.title_color, - scale=0.8, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(width * 0.5, height - 42), + size=(0, 0), + text=ba.Lstr(resource=self._r + '.titleText'), + maxwidth=370, + color=ba.app.ui.title_color, + scale=0.8, + h_align='center', + v_align='center', + ) - ba.buttonwidget(edit=btn, - button_type='backSmall', - size=(60, 60), - label=ba.charstr(ba.SpecialChar.BACK)) + ba.buttonwidget( + edit=btn, + button_type='backSmall', + size=(60, 60), + label=ba.charstr(ba.SpecialChar.BACK), + ) v = height - 70.0 v -= spacing * 1.2 - ba.textwidget(parent=self._root_widget, - position=(15, v - 26), - size=(width - 30, 30), - maxwidth=width * 0.95, - color=(0.7, 0.9, 0.7, 1.0), - scale=0.8, - text=ba.Lstr(resource=self._r + '.explanationText', - subs=[('${APP_NAME}', - ba.Lstr(resource='titleText')), - ('${REMOTE_APP_NAME}', - get_remote_app_name())]), - max_height=100, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(15, v - 26), + size=(width - 30, 30), + maxwidth=width * 0.95, + color=(0.7, 0.9, 0.7, 1.0), + scale=0.8, + text=ba.Lstr( + resource=self._r + '.explanationText', + subs=[ + ('${APP_NAME}', ba.Lstr(resource='titleText')), + ('${REMOTE_APP_NAME}', get_remote_app_name()), + ], + ), + max_height=100, + h_align='center', + v_align='center', + ) v -= 90 # hmm the itms:// version doesnt bounce through safari but is kinda # apple-specific-ish # Update: now we just show link to the remote webpage. - ba.textwidget(parent=self._root_widget, - position=(width * 0.5, v + 5), - size=(0, 0), - color=(0.7, 0.9, 0.7, 1.0), - scale=1.4, - text='bombsquadgame.com/remote', - maxwidth=width * 0.95, - max_height=60, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(width * 0.5, v + 5), + size=(0, 0), + color=(0.7, 0.9, 0.7, 1.0), + scale=1.4, + text='bombsquadgame.com/remote', + maxwidth=width * 0.95, + max_height=60, + h_align='center', + v_align='center', + ) v -= 30 - ba.textwidget(parent=self._root_widget, - position=(width * 0.5, v - 35), - size=(0, 0), - color=(0.7, 0.9, 0.7, 0.8), - scale=0.65, - text=ba.Lstr(resource=self._r + '.bestResultsText'), - maxwidth=width * 0.95, - max_height=height * 0.19, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(width * 0.5, v - 35), + size=(0, 0), + color=(0.7, 0.9, 0.7, 0.8), + scale=0.65, + text=ba.Lstr(resource=self._r + '.bestResultsText'), + maxwidth=width * 0.95, + max_height=height * 0.19, + h_align='center', + v_align='center', + ) ba.checkboxwidget( parent=self._root_widget, @@ -103,7 +128,8 @@ class RemoteAppSettingsWindow(ba.Window): value=not ba.app.config.resolve('Enable Remote App'), autoselect=True, text=ba.Lstr(resource='disableRemoteAppConnectionsText'), - on_value_change_call=self._on_check_changed) + on_value_change_call=self._on_check_changed, + ) def _on_check_changed(self, value: bool) -> None: cfg = ba.app.config @@ -112,7 +138,10 @@ class RemoteAppSettingsWindow(ba.Window): def _back(self) -> None: from bastd.ui.settings import controls + ba.containerwidget(edit=self._root_widget, transition='out_right') ba.app.ui.set_main_menu_window( controls.ControlsSettingsWindow( - transition='in_left').get_root_widget()) + transition='in_left' + ).get_root_widget() + ) diff --git a/dist/ba_data/python/bastd/ui/settings/testing.py b/dist/ba_data/python/bastd/ui/settings/testing.py index 53b63af..739ba41 100644 --- a/dist/ba_data/python/bastd/ui/settings/testing.py +++ b/dist/ba_data/python/bastd/ui/settings/testing.py @@ -17,22 +17,34 @@ if TYPE_CHECKING: class TestingWindow(ba.Window): """Window for conveniently testing various settings.""" - def __init__(self, - title: ba.Lstr, - entries: list[dict[str, Any]], - transition: str = 'in_right', - back_call: Callable[[], ba.Window] | None = None): + def __init__( + self, + title: ba.Lstr, + entries: list[dict[str, Any]], + transition: str = 'in_right', + back_call: Callable[[], ba.Window] | None = None, + ): uiscale = ba.app.ui.uiscale self._width = 600 self._height = 324 if uiscale is ba.UIScale.SMALL else 400 self._entries = copy.deepcopy(entries) self._back_call = back_call - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height), - transition=transition, - scale=(2.5 if uiscale is ba.UIScale.SMALL else - 1.2 if uiscale is ba.UIScale.MEDIUM else 1.0), - stack_offset=(0, -28) if uiscale is ba.UIScale.SMALL else (0, 0))) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height), + transition=transition, + scale=( + 2.5 + if uiscale is ba.UIScale.SMALL + else 1.2 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + stack_offset=(0, -28) + if uiscale is ba.UIScale.SMALL + else (0, 0), + ) + ) self._back_button = btn = ba.buttonwidget( parent=self._root_widget, autoselect=True, @@ -42,20 +54,25 @@ class TestingWindow(ba.Window): text_scale=1.2, label=ba.Lstr(resource='backText'), button_type='back', - on_activate_call=self._do_back) - ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, self._height - 35), - size=(0, 0), - color=ba.app.ui.title_color, - h_align='center', - v_align='center', - maxwidth=245, - text=title) + on_activate_call=self._do_back, + ) + ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, self._height - 35), + size=(0, 0), + color=ba.app.ui.title_color, + h_align='center', + v_align='center', + maxwidth=245, + text=title, + ) - ba.buttonwidget(edit=self._back_button, - button_type='backSmall', - size=(60, 60), - label=ba.charstr(ba.SpecialChar.BACK)) + ba.buttonwidget( + edit=self._back_button, + button_type='backSmall', + size=(60, 60), + label=ba.charstr(ba.SpecialChar.BACK), + ) ba.textwidget( parent=self._root_widget, @@ -65,7 +82,8 @@ class TestingWindow(ba.Window): h_align='center', v_align='center', maxwidth=self._width * 0.75, - text=ba.Lstr(resource='settingsWindowAdvanced.forTestingText')) + text=ba.Lstr(resource='settingsWindowAdvanced.forTestingText'), + ) ba.containerwidget(edit=self._root_widget, cancel_button=btn) self._scroll_width = self._width - 130 self._scroll_height = self._height - 140 @@ -73,17 +91,19 @@ class TestingWindow(ba.Window): parent=self._root_widget, size=(self._scroll_width, self._scroll_height), highlight=False, - position=((self._width - self._scroll_width) * 0.5, 40)) + position=((self._width - self._scroll_width) * 0.5, 40), + ) ba.containerwidget(edit=self._scrollwidget, claims_left_right=True) self._spacing = 50 self._sub_width = self._scroll_width * 0.95 self._sub_height = 50 + len(self._entries) * self._spacing + 60 - self._subcontainer = ba.containerwidget(parent=self._scrollwidget, - size=(self._sub_width, - self._sub_height), - background=False) + self._subcontainer = ba.containerwidget( + parent=self._scrollwidget, + size=(self._sub_width, self._sub_height), + background=False, + ) h = 230 v = self._sub_height - 48 @@ -95,46 +115,52 @@ class TestingWindow(ba.Window): # If we haven't yet, record the default value for this name so # we can reset if we want.. if entry_name not in ba.app.value_test_defaults: - ba.app.value_test_defaults[entry_name] = ( - ba.internal.value_test(entry_name)) + ba.app.value_test_defaults[entry_name] = ba.internal.value_test( + entry_name + ) - ba.textwidget(parent=self._subcontainer, - position=(h, v), - size=(0, 0), - h_align='right', - v_align='center', - maxwidth=200, - text=entry['label']) - btn = ba.buttonwidget(parent=self._subcontainer, - position=(h + 20, v - 19), - size=(40, 40), - autoselect=True, - repeat=True, - left_widget=self._back_button, - button_type='square', - label='-', - on_activate_call=ba.Call( - self._on_minus_press, entry['name'])) + ba.textwidget( + parent=self._subcontainer, + position=(h, v), + size=(0, 0), + h_align='right', + v_align='center', + maxwidth=200, + text=entry['label'], + ) + btn = ba.buttonwidget( + parent=self._subcontainer, + position=(h + 20, v - 19), + size=(40, 40), + autoselect=True, + repeat=True, + left_widget=self._back_button, + button_type='square', + label='-', + on_activate_call=ba.Call(self._on_minus_press, entry['name']), + ) if i == 0: ba.widget(edit=btn, up_widget=self._back_button) # pylint: disable=consider-using-f-string - entry['widget'] = ba.textwidget(parent=self._subcontainer, - position=(h + 100, v), - size=(0, 0), - h_align='center', - v_align='center', - maxwidth=60, - text='%.4g' % - ba.internal.value_test(entry_name)) - btn = ba.buttonwidget(parent=self._subcontainer, - position=(h + 140, v - 19), - size=(40, 40), - autoselect=True, - repeat=True, - button_type='square', - label='+', - on_activate_call=ba.Call( - self._on_plus_press, entry['name'])) + entry['widget'] = ba.textwidget( + parent=self._subcontainer, + position=(h + 100, v), + size=(0, 0), + h_align='center', + v_align='center', + maxwidth=60, + text='%.4g' % ba.internal.value_test(entry_name), + ) + btn = ba.buttonwidget( + parent=self._subcontainer, + position=(h + 140, v - 19), + size=(40, 40), + autoselect=True, + repeat=True, + button_type='square', + label='+', + on_activate_call=ba.Call(self._on_plus_press, entry['name']), + ) if i == 0: ba.widget(edit=btn, up_widget=self._back_button) v -= self._spacing @@ -146,7 +172,8 @@ class TestingWindow(ba.Window): position=(self._sub_width * 0.5 - 100, v), label=ba.Lstr(resource='settingsWindowAdvanced.resetText'), right_widget=btn, - on_activate_call=self._on_reset_press) + on_activate_call=self._on_reset_press, + ) def _get_entry(self, name: str) -> dict[str, Any]: for entry in self._entries: @@ -158,29 +185,40 @@ class TestingWindow(ba.Window): for entry in self._entries: ba.internal.value_test( entry['name'], - absolute=ba.app.value_test_defaults[entry['name']]) + absolute=ba.app.value_test_defaults[entry['name']], + ) # pylint: disable=consider-using-f-string - ba.textwidget(edit=entry['widget'], - text='%.4g' % ba.internal.value_test(entry['name'])) + ba.textwidget( + edit=entry['widget'], + text='%.4g' % ba.internal.value_test(entry['name']), + ) def _on_minus_press(self, entry_name: str) -> None: entry = self._get_entry(entry_name) ba.internal.value_test(entry['name'], change=-entry['increment']) # pylint: disable=consider-using-f-string - ba.textwidget(edit=entry['widget'], - text='%.4g' % ba.internal.value_test(entry['name'])) + ba.textwidget( + edit=entry['widget'], + text='%.4g' % ba.internal.value_test(entry['name']), + ) def _on_plus_press(self, entry_name: str) -> None: entry = self._get_entry(entry_name) ba.internal.value_test(entry['name'], change=entry['increment']) # pylint: disable=consider-using-f-string - ba.textwidget(edit=entry['widget'], - text='%.4g' % ba.internal.value_test(entry['name'])) + ba.textwidget( + edit=entry['widget'], + text='%.4g' % ba.internal.value_test(entry['name']), + ) def _do_back(self) -> None: # pylint: disable=cyclic-import from bastd.ui.settings.advanced import AdvancedSettingsWindow + ba.containerwidget(edit=self._root_widget, transition='out_right') - backwin = (self._back_call() if self._back_call is not None else - AdvancedSettingsWindow(transition='in_left')) + backwin = ( + self._back_call() + if self._back_call is not None + else AdvancedSettingsWindow(transition='in_left') + ) ba.app.ui.set_main_menu_window(backwin.get_root_widget()) diff --git a/dist/ba_data/python/bastd/ui/settings/touchscreen.py b/dist/ba_data/python/bastd/ui/settings/touchscreen.py index f8d7979..c1140cc 100644 --- a/dist/ba_data/python/bastd/ui/settings/touchscreen.py +++ b/dist/ba_data/python/bastd/ui/settings/touchscreen.py @@ -28,34 +28,48 @@ class TouchscreenSettingsWindow(ba.Window): ba.internal.set_touchscreen_editing(True) uiscale = ba.app.ui.uiscale - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height), - transition='in_right', - scale=(1.9 if uiscale is ba.UIScale.SMALL else - 1.55 if uiscale is ba.UIScale.MEDIUM else 1.2))) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height), + transition='in_right', + scale=( + 1.9 + if uiscale is ba.UIScale.SMALL + else 1.55 + if uiscale is ba.UIScale.MEDIUM + else 1.2 + ), + ) + ) - btn = ba.buttonwidget(parent=self._root_widget, - position=(55, self._height - 60), - size=(120, 60), - label=ba.Lstr(resource='backText'), - button_type='back', - scale=0.8, - on_activate_call=self._back) + btn = ba.buttonwidget( + parent=self._root_widget, + position=(55, self._height - 60), + size=(120, 60), + label=ba.Lstr(resource='backText'), + button_type='back', + scale=0.8, + on_activate_call=self._back, + ) ba.containerwidget(edit=self._root_widget, cancel_button=btn) - ba.textwidget(parent=self._root_widget, - position=(25, self._height - 50), - size=(self._width, 25), - text=ba.Lstr(resource=self._r + '.titleText'), - color=ba.app.ui.title_color, - maxwidth=280, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(25, self._height - 50), + size=(self._width, 25), + text=ba.Lstr(resource=self._r + '.titleText'), + color=ba.app.ui.title_color, + maxwidth=280, + h_align='center', + v_align='center', + ) - ba.buttonwidget(edit=btn, - button_type='backSmall', - size=(60, 60), - label=ba.charstr(ba.SpecialChar.BACK)) + ba.buttonwidget( + edit=btn, + button_type='backSmall', + size=(60, 60), + label=ba.charstr(ba.SpecialChar.BACK), + ) self._scroll_width = self._width - 100 self._scroll_height = self._height - 110 @@ -64,19 +78,23 @@ class TouchscreenSettingsWindow(ba.Window): self._scrollwidget = ba.scrollwidget( parent=self._root_widget, - position=((self._width - self._scroll_width) * 0.5, - self._height - 65 - self._scroll_height), + position=( + (self._width - self._scroll_width) * 0.5, + self._height - 65 - self._scroll_height, + ), size=(self._scroll_width, self._scroll_height), claims_left_right=True, claims_tab=True, - selection_loops_to_parent=True) - self._subcontainer = ba.containerwidget(parent=self._scrollwidget, - size=(self._sub_width, - self._sub_height), - background=False, - claims_left_right=True, - claims_tab=True, - selection_loops_to_parent=True) + selection_loops_to_parent=True, + ) + self._subcontainer = ba.containerwidget( + parent=self._scrollwidget, + size=(self._sub_width, self._sub_height), + background=False, + claims_left_right=True, + claims_tab=True, + selection_loops_to_parent=True, + ) self._build_gui() def _build_gui(self) -> None: @@ -91,116 +109,137 @@ class TouchscreenSettingsWindow(ba.Window): v = self._sub_height - 85 clr = (0.8, 0.8, 0.8, 1.0) clr2 = (0.8, 0.8, 0.8) - ba.textwidget(parent=self._subcontainer, - position=(-10, v + 43), - size=(self._sub_width, 25), - text=ba.Lstr(resource=self._r + '.swipeInfoText'), - flatness=1.0, - color=(0, 0.9, 0.1, 0.7), - maxwidth=self._sub_width * 0.9, - scale=0.55, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._subcontainer, + position=(-10, v + 43), + size=(self._sub_width, 25), + text=ba.Lstr(resource=self._r + '.swipeInfoText'), + flatness=1.0, + color=(0, 0.9, 0.1, 0.7), + maxwidth=self._sub_width * 0.9, + scale=0.55, + h_align='center', + v_align='center', + ) cur_val = ba.app.config.get('Touch Movement Control Type', 'swipe') - ba.textwidget(parent=self._subcontainer, - position=(h, v - 2), - size=(0, 30), - text=ba.Lstr(resource=self._r + '.movementText'), - maxwidth=190, - color=clr, - v_align='center') - cb1 = ba.checkboxwidget(parent=self._subcontainer, - position=(h + 220, v), - size=(170, 30), - text=ba.Lstr(resource=self._r + - '.joystickText'), - maxwidth=100, - textcolor=clr2, - scale=0.9) - cb2 = ba.checkboxwidget(parent=self._subcontainer, - position=(h + 357, v), - size=(170, 30), - text=ba.Lstr(resource=self._r + '.swipeText'), - maxwidth=100, - textcolor=clr2, - value=False, - scale=0.9) - make_radio_group((cb1, cb2), ('joystick', 'swipe'), cur_val, - self._movement_changed) + ba.textwidget( + parent=self._subcontainer, + position=(h, v - 2), + size=(0, 30), + text=ba.Lstr(resource=self._r + '.movementText'), + maxwidth=190, + color=clr, + v_align='center', + ) + cb1 = ba.checkboxwidget( + parent=self._subcontainer, + position=(h + 220, v), + size=(170, 30), + text=ba.Lstr(resource=self._r + '.joystickText'), + maxwidth=100, + textcolor=clr2, + scale=0.9, + ) + cb2 = ba.checkboxwidget( + parent=self._subcontainer, + position=(h + 357, v), + size=(170, 30), + text=ba.Lstr(resource=self._r + '.swipeText'), + maxwidth=100, + textcolor=clr2, + value=False, + scale=0.9, + ) + make_radio_group( + (cb1, cb2), ('joystick', 'swipe'), cur_val, self._movement_changed + ) v -= 50 - ConfigNumberEdit(parent=self._subcontainer, - position=(h, v), - xoffset=65, - configkey='Touch Controls Scale Movement', - displayname=ba.Lstr(resource=self._r + - '.movementControlScaleText'), - changesound=False, - minval=0.1, - maxval=4.0, - increment=0.1) + ConfigNumberEdit( + parent=self._subcontainer, + position=(h, v), + xoffset=65, + configkey='Touch Controls Scale Movement', + displayname=ba.Lstr(resource=self._r + '.movementControlScaleText'), + changesound=False, + minval=0.1, + maxval=4.0, + increment=0.1, + ) v -= 50 cur_val = ba.app.config.get('Touch Action Control Type', 'buttons') - ba.textwidget(parent=self._subcontainer, - position=(h, v - 2), - size=(0, 30), - text=ba.Lstr(resource=self._r + '.actionsText'), - maxwidth=190, - color=clr, - v_align='center') - cb1 = ba.checkboxwidget(parent=self._subcontainer, - position=(h + 220, v), - size=(170, 30), - text=ba.Lstr(resource=self._r + - '.buttonsText'), - maxwidth=100, - textcolor=clr2, - scale=0.9) - cb2 = ba.checkboxwidget(parent=self._subcontainer, - position=(h + 357, v), - size=(170, 30), - text=ba.Lstr(resource=self._r + '.swipeText'), - maxwidth=100, - textcolor=clr2, - scale=0.9) - make_radio_group((cb1, cb2), ('buttons', 'swipe'), cur_val, - self._actions_changed) + ba.textwidget( + parent=self._subcontainer, + position=(h, v - 2), + size=(0, 30), + text=ba.Lstr(resource=self._r + '.actionsText'), + maxwidth=190, + color=clr, + v_align='center', + ) + cb1 = ba.checkboxwidget( + parent=self._subcontainer, + position=(h + 220, v), + size=(170, 30), + text=ba.Lstr(resource=self._r + '.buttonsText'), + maxwidth=100, + textcolor=clr2, + scale=0.9, + ) + cb2 = ba.checkboxwidget( + parent=self._subcontainer, + position=(h + 357, v), + size=(170, 30), + text=ba.Lstr(resource=self._r + '.swipeText'), + maxwidth=100, + textcolor=clr2, + scale=0.9, + ) + make_radio_group( + (cb1, cb2), ('buttons', 'swipe'), cur_val, self._actions_changed + ) v -= 50 - ConfigNumberEdit(parent=self._subcontainer, - position=(h, v), - xoffset=65, - configkey='Touch Controls Scale Actions', - displayname=ba.Lstr(resource=self._r + - '.actionControlScaleText'), - changesound=False, - minval=0.1, - maxval=4.0, - increment=0.1) + ConfigNumberEdit( + parent=self._subcontainer, + position=(h, v), + xoffset=65, + configkey='Touch Controls Scale Actions', + displayname=ba.Lstr(resource=self._r + '.actionControlScaleText'), + changesound=False, + minval=0.1, + maxval=4.0, + increment=0.1, + ) v -= 50 - ConfigCheckBox(parent=self._subcontainer, - position=(h, v), - size=(400, 30), - maxwidth=400, - configkey='Touch Controls Swipe Hidden', - displayname=ba.Lstr(resource=self._r + - '.swipeControlsHiddenText')) + ConfigCheckBox( + parent=self._subcontainer, + position=(h, v), + size=(400, 30), + maxwidth=400, + configkey='Touch Controls Swipe Hidden', + displayname=ba.Lstr(resource=self._r + '.swipeControlsHiddenText'), + ) v -= 65 - ba.buttonwidget(parent=self._subcontainer, - position=(self._sub_width * 0.5 - 70, v), - size=(170, 60), - label=ba.Lstr(resource=self._r + '.resetText'), - scale=0.75, - on_activate_call=self._reset) + ba.buttonwidget( + parent=self._subcontainer, + position=(self._sub_width * 0.5 - 70, v), + size=(170, 60), + label=ba.Lstr(resource=self._r + '.resetText'), + scale=0.75, + on_activate_call=self._reset, + ) - ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, 38), - size=(0, 0), - h_align='center', - text=ba.Lstr(resource=self._r + '.dragControlsText'), - maxwidth=self._width * 0.8, - scale=0.65, - color=(1, 1, 1, 0.4)) + ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, 38), + size=(0, 0), + h_align='center', + text=ba.Lstr(resource=self._r + '.dragControlsText'), + maxwidth=self._width * 0.8, + scale=0.65, + color=(1, 1, 1, 0.4), + ) def _actions_changed(self, v: str) -> None: cfg = ba.app.config @@ -215,11 +254,16 @@ class TouchscreenSettingsWindow(ba.Window): def _reset(self) -> None: cfg = ba.app.config cfgkeys = [ - 'Touch Movement Control Type', 'Touch Action Control Type', - 'Touch Controls Scale', 'Touch Controls Scale Movement', - 'Touch Controls Scale Actions', 'Touch Controls Swipe Hidden', - 'Touch DPad X', 'Touch DPad Y', 'Touch Buttons X', - 'Touch Buttons Y' + 'Touch Movement Control Type', + 'Touch Action Control Type', + 'Touch Controls Scale', + 'Touch Controls Scale Movement', + 'Touch Controls Scale Actions', + 'Touch Controls Swipe Hidden', + 'Touch DPad X', + 'Touch DPad Y', + 'Touch Buttons X', + 'Touch Buttons Y', ] for cfgkey in cfgkeys: if cfgkey in cfg: @@ -229,8 +273,11 @@ class TouchscreenSettingsWindow(ba.Window): def _back(self) -> None: from bastd.ui.settings import controls + ba.containerwidget(edit=self._root_widget, transition='out_right') ba.app.ui.set_main_menu_window( controls.ControlsSettingsWindow( - transition='in_left').get_root_widget()) + transition='in_left' + ).get_root_widget() + ) ba.internal.set_touchscreen_editing(False) diff --git a/dist/ba_data/python/bastd/ui/settings/vrtesting.py b/dist/ba_data/python/bastd/ui/settings/vrtesting.py index a6723f7..43f1815 100644 --- a/dist/ba_data/python/bastd/ui/settings/vrtesting.py +++ b/dist/ba_data/python/bastd/ui/settings/vrtesting.py @@ -26,17 +26,17 @@ class VRTestingWindow(TestingWindow): { 'name': 'timeWarpDebug', 'label': 'Time Warp Debug', - 'increment': 1.0 + 'increment': 1.0, }, { 'name': 'chromaticAberrationCorrection', 'label': 'Chromatic Aberration Correction', - 'increment': 1.0 + 'increment': 1.0, }, { 'name': 'vrMinimumVSyncs', 'label': 'Minimum Vsyncs', - 'increment': 1.0 + 'increment': 1.0, }, # {'name':'eyeOffsX','label':'Eye IPD','increment':0.001} ] @@ -46,45 +46,45 @@ class VRTestingWindow(TestingWindow): # {'name':'eyeOffsY','label':'Eye Offset Y','increment':0.01}, # {'name':'eyeOffsZ','label':'Eye Offset Z','increment':0.005}] # everyone gets head-scale - entries += [{ - 'name': 'headScale', - 'label': 'Head Scale', - 'increment': 1.0 - }] + entries += [ + {'name': 'headScale', 'label': 'Head Scale', 'increment': 1.0} + ] # and everyone gets all these.. entries += [ { 'name': 'vrCamOffsetY', 'label': 'In-Game Cam Offset Y', - 'increment': 0.1 + 'increment': 0.1, }, { 'name': 'vrCamOffsetZ', 'label': 'In-Game Cam Offset Z', - 'increment': 0.1 + 'increment': 0.1, }, { 'name': 'vrOverlayScale', 'label': 'Overlay Scale', - 'increment': 0.025 + 'increment': 0.025, }, { 'name': 'allowCameraMovement', 'label': 'Allow Camera Movement', - 'increment': 1.0 + 'increment': 1.0, }, { 'name': 'cameraPanSpeedScale', 'label': 'Camera Movement Speed', - 'increment': 0.1 + 'increment': 0.1, }, { 'name': 'showOverlayBounds', 'label': 'Show Overlay Bounds', - 'increment': 1 + 'increment': 1, }, ] super().__init__( - ba.Lstr(resource='settingsWindowAdvanced.vrTestingText'), entries, - transition) + ba.Lstr(resource='settingsWindowAdvanced.vrTestingText'), + entries, + transition, + ) diff --git a/dist/ba_data/python/bastd/ui/settings/xbox360controller.py b/dist/ba_data/python/bastd/ui/settings/xbox360controller.py index bdec91d..546f8f9 100644 --- a/dist/ba_data/python/bastd/ui/settings/xbox360controller.py +++ b/dist/ba_data/python/bastd/ui/settings/xbox360controller.py @@ -22,66 +22,83 @@ class XBox360ControllerSettingsWindow(ba.Window): height = 300 if ba.internal.is_running_on_fire_tv() else 485 spacing = 40 uiscale = ba.app.ui.uiscale - super().__init__(root_widget=ba.containerwidget( - size=(width, height), - transition='in_right', - scale=(1.4 if uiscale is ba.UIScale.SMALL else - 1.4 if uiscale is ba.UIScale.MEDIUM else 1.0))) + super().__init__( + root_widget=ba.containerwidget( + size=(width, height), + transition='in_right', + scale=( + 1.4 + if uiscale is ba.UIScale.SMALL + else 1.4 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + ) + ) - btn = ba.buttonwidget(parent=self._root_widget, - position=(35, height - 65), - size=(120, 60), - scale=0.84, - label=ba.Lstr(resource='backText'), - button_type='back', - autoselect=True, - on_activate_call=self._back) + btn = ba.buttonwidget( + parent=self._root_widget, + position=(35, height - 65), + size=(120, 60), + scale=0.84, + label=ba.Lstr(resource='backText'), + button_type='back', + autoselect=True, + on_activate_call=self._back, + ) ba.containerwidget(edit=self._root_widget, cancel_button=btn) - ba.textwidget(parent=self._root_widget, - position=(width * 0.5, height - 42), - size=(0, 0), - scale=0.85, - text=ba.Lstr(resource=self._r + '.titleText', - subs=[('${APP_NAME}', - ba.Lstr(resource='titleText'))]), - color=ba.app.ui.title_color, - maxwidth=400, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(width * 0.5, height - 42), + size=(0, 0), + scale=0.85, + text=ba.Lstr( + resource=self._r + '.titleText', + subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))], + ), + color=ba.app.ui.title_color, + maxwidth=400, + h_align='center', + v_align='center', + ) - ba.buttonwidget(edit=btn, - button_type='backSmall', - size=(60, 60), - label=ba.charstr(ba.SpecialChar.BACK)) + ba.buttonwidget( + edit=btn, + button_type='backSmall', + size=(60, 60), + label=ba.charstr(ba.SpecialChar.BACK), + ) v = height - 70 v -= spacing if ba.internal.is_running_on_fire_tv(): - ba.textwidget(parent=self._root_widget, - position=(width * 0.5, height * 0.47), - size=(0, 0), - color=(0.7, 0.9, 0.7, 1.0), - maxwidth=width * 0.95, - max_height=height * 0.75, - scale=0.7, - text=ba.Lstr(resource=self._r + - '.ouyaInstructionsText'), - h_align='center', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(width * 0.5, height * 0.47), + size=(0, 0), + color=(0.7, 0.9, 0.7, 1.0), + maxwidth=width * 0.95, + max_height=height * 0.75, + scale=0.7, + text=ba.Lstr(resource=self._r + '.ouyaInstructionsText'), + h_align='center', + v_align='center', + ) else: - ba.textwidget(parent=self._root_widget, - position=(width * 0.5, v - 1), - size=(0, 0), - color=(0.7, 0.9, 0.7, 1.0), - maxwidth=width * 0.95, - max_height=height * 0.22, - text=ba.Lstr(resource=self._r + - '.macInstructionsText'), - scale=0.7, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(width * 0.5, v - 1), + size=(0, 0), + color=(0.7, 0.9, 0.7, 1.0), + maxwidth=width * 0.95, + max_height=height * 0.22, + text=ba.Lstr(resource=self._r + '.macInstructionsText'), + scale=0.7, + h_align='center', + v_align='center', + ) v -= 90 b_width = 300 btn = ba.buttonwidget( @@ -92,24 +109,30 @@ class XBox360ControllerSettingsWindow(ba.Window): autoselect=True, on_activate_call=ba.Call( ba.open_url, - 'https://github.com/360Controller/360Controller/releases')) + 'https://github.com/360Controller/360Controller/releases', + ), + ) ba.containerwidget(edit=self._root_widget, start_button=btn) v -= 60 - ba.textwidget(parent=self._root_widget, - position=(width * 0.5, v - 85), - size=(0, 0), - color=(0.7, 0.9, 0.7, 1.0), - maxwidth=width * 0.95, - max_height=height * 0.46, - scale=0.7, - text=ba.Lstr(resource=self._r + - '.macInstructions2Text'), - h_align='center', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(width * 0.5, v - 85), + size=(0, 0), + color=(0.7, 0.9, 0.7, 1.0), + maxwidth=width * 0.95, + max_height=height * 0.46, + scale=0.7, + text=ba.Lstr(resource=self._r + '.macInstructions2Text'), + h_align='center', + v_align='center', + ) def _back(self) -> None: from bastd.ui.settings import controls + ba.containerwidget(edit=self._root_widget, transition='out_right') ba.app.ui.set_main_menu_window( controls.ControlsSettingsWindow( - transition='in_left').get_root_widget()) + transition='in_left' + ).get_root_widget() + ) diff --git a/dist/ba_data/python/bastd/ui/soundtrack/browser.py b/dist/ba_data/python/bastd/ui/soundtrack/browser.py index a4bf0dd..457b441 100644 --- a/dist/ba_data/python/bastd/ui/soundtrack/browser.py +++ b/dist/ba_data/python/bastd/ui/soundtrack/browser.py @@ -17,9 +17,11 @@ if TYPE_CHECKING: class SoundtrackBrowserWindow(ba.Window): """Window for browsing soundtracks.""" - def __init__(self, - transition: str = 'in_right', - origin_widget: ba.Widget | None = None): + def __init__( + self, + transition: str = 'in_right', + origin_widget: ba.Widget | None = None, + ): # pylint: disable=too-many-locals # pylint: disable=too-many-statements @@ -37,20 +39,35 @@ class SoundtrackBrowserWindow(ba.Window): uiscale = ba.app.ui.uiscale self._width = 800 if uiscale is ba.UIScale.SMALL else 600 x_inset = 100 if uiscale is ba.UIScale.SMALL else 0 - self._height = (340 if uiscale is ba.UIScale.SMALL else - 370 if uiscale is ba.UIScale.MEDIUM else 440) + self._height = ( + 340 + if uiscale is ba.UIScale.SMALL + else 370 + if uiscale is ba.UIScale.MEDIUM + else 440 + ) spacing = 40.0 v = self._height - 40.0 v -= spacing * 1.0 - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height), - transition=transition, - toolbar_visibility='menu_minimal', - scale_origin_stack_offset=scale_origin, - scale=(2.3 if uiscale is ba.UIScale.SMALL else - 1.6 if uiscale is ba.UIScale.MEDIUM else 1.0), - stack_offset=(0, -18) if uiscale is ba.UIScale.SMALL else (0, 0))) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height), + transition=transition, + toolbar_visibility='menu_minimal', + scale_origin_stack_offset=scale_origin, + scale=( + 2.3 + if uiscale is ba.UIScale.SMALL + else 1.6 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + stack_offset=(0, -18) + if uiscale is ba.UIScale.SMALL + else (0, 0), + ) + ) if ba.app.ui.use_toolbars and uiscale is ba.UIScale.SMALL: self._back_button = None @@ -62,19 +79,24 @@ class SoundtrackBrowserWindow(ba.Window): scale=0.8, label=ba.Lstr(resource='backText'), button_type='back', - autoselect=True) - ba.buttonwidget(edit=self._back_button, - button_type='backSmall', - size=(60, 60), - label=ba.charstr(ba.SpecialChar.BACK)) - ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, self._height - 35), - size=(0, 0), - maxwidth=300, - text=ba.Lstr(resource=self._r + '.titleText'), - color=ba.app.ui.title_color, - h_align='center', - v_align='center') + autoselect=True, + ) + ba.buttonwidget( + edit=self._back_button, + button_type='backSmall', + size=(60, 60), + label=ba.charstr(ba.SpecialChar.BACK), + ) + ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, self._height - 35), + size=(0, 0), + maxwidth=300, + text=ba.Lstr(resource=self._r + '.titleText'), + color=ba.app.ui.title_color, + h_align='center', + v_align='center', + ) h = 43 + x_inset v = self._height - 60 @@ -83,8 +105,13 @@ class SoundtrackBrowserWindow(ba.Window): lock_tex = ba.gettexture('lock') self._lock_images: list[ba.Widget] = [] - scl = (1.0 if uiscale is ba.UIScale.SMALL else - 1.13 if uiscale is ba.UIScale.MEDIUM else 1.4) + scl = ( + 1.0 + if uiscale is ba.UIScale.SMALL + else 1.13 + if uiscale is ba.UIScale.MEDIUM + else 1.4 + ) v -= 60.0 * scl self._new_button = btn = ba.buttonwidget( parent=self._root_widget, @@ -96,18 +123,23 @@ class SoundtrackBrowserWindow(ba.Window): autoselect=True, textcolor=b_textcolor, text_scale=0.7, - label=ba.Lstr(resource=self._r + '.newText')) + label=ba.Lstr(resource=self._r + '.newText'), + ) self._lock_images.append( - ba.imagewidget(parent=self._root_widget, - size=(30, 30), - draw_controller=btn, - position=(h - 10, v + 55.0 * scl - 28), - texture=lock_tex)) + ba.imagewidget( + parent=self._root_widget, + size=(30, 30), + draw_controller=btn, + position=(h - 10, v + 55.0 * scl - 28), + texture=lock_tex, + ) + ) if self._back_button is None: ba.widget( edit=btn, - left_widget=ba.internal.get_special_widget('back_button')) + left_widget=ba.internal.get_special_widget('back_button'), + ) v -= 60.0 * scl self._edit_button = btn = ba.buttonwidget( @@ -120,17 +152,22 @@ class SoundtrackBrowserWindow(ba.Window): autoselect=True, textcolor=b_textcolor, text_scale=0.7, - label=ba.Lstr(resource=self._r + '.editText')) + label=ba.Lstr(resource=self._r + '.editText'), + ) self._lock_images.append( - ba.imagewidget(parent=self._root_widget, - size=(30, 30), - draw_controller=btn, - position=(h - 10, v + 55.0 * scl - 28), - texture=lock_tex)) + ba.imagewidget( + parent=self._root_widget, + size=(30, 30), + draw_controller=btn, + position=(h - 10, v + 55.0 * scl - 28), + texture=lock_tex, + ) + ) if self._back_button is None: ba.widget( edit=btn, - left_widget=ba.internal.get_special_widget('back_button')) + left_widget=ba.internal.get_special_widget('back_button'), + ) v -= 60.0 * scl self._duplicate_button = btn = ba.buttonwidget( @@ -143,17 +180,22 @@ class SoundtrackBrowserWindow(ba.Window): color=b_color, textcolor=b_textcolor, text_scale=0.7, - label=ba.Lstr(resource=self._r + '.duplicateText')) + label=ba.Lstr(resource=self._r + '.duplicateText'), + ) self._lock_images.append( - ba.imagewidget(parent=self._root_widget, - size=(30, 30), - draw_controller=btn, - position=(h - 10, v + 55.0 * scl - 28), - texture=lock_tex)) + ba.imagewidget( + parent=self._root_widget, + size=(30, 30), + draw_controller=btn, + position=(h - 10, v + 55.0 * scl - 28), + texture=lock_tex, + ) + ) if self._back_button is None: ba.widget( edit=btn, - left_widget=ba.internal.get_special_widget('back_button')) + left_widget=ba.internal.get_special_widget('back_button'), + ) v -= 60.0 * scl self._delete_button = btn = ba.buttonwidget( @@ -166,23 +208,30 @@ class SoundtrackBrowserWindow(ba.Window): autoselect=True, textcolor=b_textcolor, text_scale=0.7, - label=ba.Lstr(resource=self._r + '.deleteText')) + label=ba.Lstr(resource=self._r + '.deleteText'), + ) self._lock_images.append( - ba.imagewidget(parent=self._root_widget, - size=(30, 30), - draw_controller=btn, - position=(h - 10, v + 55.0 * scl - 28), - texture=lock_tex)) + ba.imagewidget( + parent=self._root_widget, + size=(30, 30), + draw_controller=btn, + position=(h - 10, v + 55.0 * scl - 28), + texture=lock_tex, + ) + ) if self._back_button is None: ba.widget( edit=btn, - left_widget=ba.internal.get_special_widget('back_button')) + left_widget=ba.internal.get_special_widget('back_button'), + ) # Keep our lock images up to date/etc. - self._update_timer = ba.Timer(1.0, - ba.WeakCall(self._update), - timetype=ba.TimeType.REAL, - repeat=True) + self._update_timer = ba.Timer( + 1.0, + ba.WeakCall(self._update), + timetype=ba.TimeType.REAL, + repeat=True, + ) self._update() v = self._height - 65 @@ -192,11 +241,15 @@ class SoundtrackBrowserWindow(ba.Window): parent=self._root_widget, position=(152 + x_inset, v), highlight=False, - size=(self._width - (205 + 2 * x_inset), scroll_height)) - ba.widget(edit=self._scrollwidget, - left_widget=self._new_button, - right_widget=ba.internal.get_special_widget('party_button') - if ba.app.ui.use_toolbars else self._scrollwidget) + size=(self._width - (205 + 2 * x_inset), scroll_height), + ) + ba.widget( + edit=self._scrollwidget, + left_widget=self._new_button, + right_widget=ba.internal.get_special_widget('party_button') + if ba.app.ui.use_toolbars + else self._scrollwidget, + ) self._col = ba.columnwidget(parent=scrollwidget, border=2, margin=0) self._soundtracks: dict[str, Any] | None = None @@ -206,13 +259,14 @@ class SoundtrackBrowserWindow(ba.Window): self._allow_changing_soundtracks = False self._refresh() if self._back_button is not None: - ba.buttonwidget(edit=self._back_button, - on_activate_call=self._back) - ba.containerwidget(edit=self._root_widget, - cancel_button=self._back_button) + ba.buttonwidget(edit=self._back_button, on_activate_call=self._back) + ba.containerwidget( + edit=self._root_widget, cancel_button=self._back_button + ) else: - ba.containerwidget(edit=self._root_widget, - on_cancel_call=self._back) + ba.containerwidget( + edit=self._root_widget, on_cancel_call=self._back + ) def _update(self) -> None: have = ba.app.accounts_v1.have_pro_options() @@ -236,6 +290,7 @@ class SoundtrackBrowserWindow(ba.Window): # pylint: disable=cyclic-import from bastd.ui.purchase import PurchaseWindow from bastd.ui.confirm import ConfirmWindow + if not ba.app.accounts_v1.have_pro_options(): PurchaseWindow(items=['pro']) return @@ -243,18 +298,25 @@ class SoundtrackBrowserWindow(ba.Window): return if self._selected_soundtrack == '__default__': ba.playsound(ba.getsound('error')) - ba.screenmessage(ba.Lstr(resource=self._r + - '.cantDeleteDefaultText'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr(resource=self._r + '.cantDeleteDefaultText'), + color=(1, 0, 0), + ) else: ConfirmWindow( - ba.Lstr(resource=self._r + '.deleteConfirmText', - subs=[('${NAME}', self._selected_soundtrack)]), - self._do_delete_soundtrack, 450, 150) + ba.Lstr( + resource=self._r + '.deleteConfirmText', + subs=[('${NAME}', self._selected_soundtrack)], + ), + self._do_delete_soundtrack, + 450, + 150, + ) def _duplicate_soundtrack(self) -> None: # pylint: disable=cyclic-import from bastd.ui.purchase import PurchaseWindow + if not ba.app.accounts_v1.have_pro_options(): PurchaseWindow(items=['pro']) return @@ -275,7 +337,8 @@ class SoundtrackBrowserWindow(ba.Window): # Get just 'Copy' or whatnot. copy_word = copy_text.replace('${NAME}', '').strip() base_name = self._get_soundtrack_display_name( - self._selected_soundtrack).evaluate() + self._selected_soundtrack + ).evaluate() assert isinstance(base_name, str) # If it looks like a copy, strip digits and spaces off the end. @@ -317,15 +380,19 @@ class SoundtrackBrowserWindow(ba.Window): def _back(self) -> None: # pylint: disable=cyclic-import from bastd.ui.settings import audio + self._save_state() - ba.containerwidget(edit=self._root_widget, - transition=self._transition_out) + ba.containerwidget( + edit=self._root_widget, transition=self._transition_out + ) ba.app.ui.set_main_menu_window( - audio.AudioSettingsWindow(transition='in_left').get_root_widget()) + audio.AudioSettingsWindow(transition='in_left').get_root_widget() + ) def _edit_soundtrack_with_sound(self) -> None: # pylint: disable=cyclic-import from bastd.ui.purchase import PurchaseWindow + if not ba.app.accounts_v1.have_pro_options(): PurchaseWindow(items=['pro']) return @@ -336,6 +403,7 @@ class SoundtrackBrowserWindow(ba.Window): # pylint: disable=cyclic-import from bastd.ui.purchase import PurchaseWindow from bastd.ui.soundtrack.edit import SoundtrackEditWindow + if not ba.app.accounts_v1.have_pro_options(): PurchaseWindow(items=['pro']) return @@ -343,16 +411,19 @@ class SoundtrackBrowserWindow(ba.Window): return if self._selected_soundtrack == '__default__': ba.playsound(ba.getsound('error')) - ba.screenmessage(ba.Lstr(resource=self._r + - '.cantEditDefaultText'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr(resource=self._r + '.cantEditDefaultText'), + color=(1, 0, 0), + ) return self._save_state() ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.ui.set_main_menu_window( - SoundtrackEditWindow(existing_soundtrack=self._selected_soundtrack - ).get_root_widget()) + SoundtrackEditWindow( + existing_soundtrack=self._selected_soundtrack + ).get_root_widget() + ) def _get_soundtrack_display_name(self, soundtrack: str) -> ba.Lstr: if soundtrack == '__default__': @@ -361,6 +432,7 @@ class SoundtrackBrowserWindow(ba.Window): def _refresh(self, select_soundtrack: str | None = None) -> None: from efro.util import asserttype + self._allow_changing_soundtracks = False old_selection = self._selected_soundtrack @@ -391,7 +463,8 @@ class SoundtrackBrowserWindow(ba.Window): always_highlight=True, on_select_call=ba.WeakCall(self._select, pname, index), on_activate_call=self._edit_soundtrack_with_sound, - selectable=True) + selectable=True, + ) if index == 0: ba.widget(edit=txtw, up_widget=self._back_button) self._soundtrack_widgets.append(txtw) @@ -399,22 +472,26 @@ class SoundtrackBrowserWindow(ba.Window): # Select this one if the user requested it if select_soundtrack is not None: if pname == select_soundtrack: - ba.columnwidget(edit=self._col, - selected_child=txtw, - visible_child=txtw) + ba.columnwidget( + edit=self._col, selected_child=txtw, visible_child=txtw + ) else: # Select this one if it was previously selected. # Go by index if there's one. if old_selection_index is not None: if index == old_selection_index: - ba.columnwidget(edit=self._col, - selected_child=txtw, - visible_child=txtw) + ba.columnwidget( + edit=self._col, + selected_child=txtw, + visible_child=txtw, + ) else: # Otherwise look by name. if pname == old_selection: - ba.columnwidget(edit=self._col, - selected_child=txtw, - visible_child=txtw) + ba.columnwidget( + edit=self._col, + selected_child=txtw, + visible_child=txtw, + ) index += 1 # Explicitly run select callback on current one and re-enable @@ -423,21 +500,23 @@ class SoundtrackBrowserWindow(ba.Window): # Eww need to run this in a timer so it happens after our select # callbacks. With a small-enough time sometimes it happens before # anyway. Ew. need a way to just schedule a callable i guess. - ba.timer(0.1, - ba.WeakCall(self._set_allow_changing), - timetype=ba.TimeType.REAL) + ba.timer( + 0.1, + ba.WeakCall(self._set_allow_changing), + timetype=ba.TimeType.REAL, + ) def _set_allow_changing(self) -> None: self._allow_changing_soundtracks = True assert self._selected_soundtrack is not None assert self._selected_soundtrack_index is not None - self._select(self._selected_soundtrack, - self._selected_soundtrack_index) + self._select(self._selected_soundtrack, self._selected_soundtrack_index) def _new_soundtrack(self) -> None: # pylint: disable=cyclic-import from bastd.ui.purchase import PurchaseWindow from bastd.ui.soundtrack.edit import SoundtrackEditWindow + if not ba.app.accounts_v1.have_pro_options(): PurchaseWindow(items=['pro']) return diff --git a/dist/ba_data/python/bastd/ui/soundtrack/edit.py b/dist/ba_data/python/bastd/ui/soundtrack/edit.py index 5ab08ca..cb8d27c 100644 --- a/dist/ba_data/python/bastd/ui/soundtrack/edit.py +++ b/dist/ba_data/python/bastd/ui/soundtrack/edit.py @@ -17,9 +17,11 @@ if TYPE_CHECKING: class SoundtrackEditWindow(ba.Window): """Window for editing a soundtrack.""" - def __init__(self, - existing_soundtrack: str | dict[str, Any] | None, - transition: str = 'in_right'): + def __init__( + self, + existing_soundtrack: str | dict[str, Any] | None, + transition: str = 'in_right', + ): # pylint: disable=too-many-statements appconfig = ba.app.config self._r = 'editSoundtrackWindow' @@ -28,42 +30,66 @@ class SoundtrackEditWindow(ba.Window): uiscale = ba.app.ui.uiscale self._width = 848 if uiscale is ba.UIScale.SMALL else 648 x_inset = 100 if uiscale is ba.UIScale.SMALL else 0 - self._height = (395 if uiscale is ba.UIScale.SMALL else - 450 if uiscale is ba.UIScale.MEDIUM else 560) - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height), - transition=transition, - scale=(2.08 if uiscale is ba.UIScale.SMALL else - 1.5 if uiscale is ba.UIScale.MEDIUM else 1.0), - stack_offset=(0, -48) if uiscale is ba.UIScale.SMALL else ( - 0, 15) if uiscale is ba.UIScale.MEDIUM else (0, 0))) - cancel_button = ba.buttonwidget(parent=self._root_widget, - position=(38 + x_inset, - self._height - 60), - size=(160, 60), - autoselect=True, - label=ba.Lstr(resource='cancelText'), - scale=0.8) - save_button = ba.buttonwidget(parent=self._root_widget, - position=(self._width - (168 + x_inset), - self._height - 60), - autoselect=True, - size=(160, 60), - label=ba.Lstr(resource='saveText'), - scale=0.8) + self._height = ( + 395 + if uiscale is ba.UIScale.SMALL + else 450 + if uiscale is ba.UIScale.MEDIUM + else 560 + ) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height), + transition=transition, + scale=( + 2.08 + if uiscale is ba.UIScale.SMALL + else 1.5 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + stack_offset=(0, -48) + if uiscale is ba.UIScale.SMALL + else (0, 15) + if uiscale is ba.UIScale.MEDIUM + else (0, 0), + ) + ) + cancel_button = ba.buttonwidget( + parent=self._root_widget, + position=(38 + x_inset, self._height - 60), + size=(160, 60), + autoselect=True, + label=ba.Lstr(resource='cancelText'), + scale=0.8, + ) + save_button = ba.buttonwidget( + parent=self._root_widget, + position=(self._width - (168 + x_inset), self._height - 60), + autoselect=True, + size=(160, 60), + label=ba.Lstr(resource='saveText'), + scale=0.8, + ) ba.widget(edit=save_button, left_widget=cancel_button) ba.widget(edit=cancel_button, right_widget=save_button) ba.textwidget( parent=self._root_widget, position=(0, self._height - 50), size=(self._width, 25), - text=ba.Lstr(resource=self._r + - ('.editSoundtrackText' if existing_soundtrack - is not None else '.newSoundtrackText')), + text=ba.Lstr( + resource=self._r + + ( + '.editSoundtrackText' + if existing_soundtrack is not None + else '.newSoundtrackText' + ) + ), color=ba.app.ui.title_color, h_align='center', v_align='center', - maxwidth=280) + maxwidth=280, + ) v = self._height - 110 if 'Soundtracks' not in appconfig: appconfig['Soundtracks'] = {} @@ -74,7 +100,8 @@ class SoundtrackEditWindow(ba.Window): # if they passed just a name, pull info from that soundtrack if isinstance(existing_soundtrack, str): self._soundtrack = copy.deepcopy( - appconfig['Soundtracks'][existing_soundtrack]) + appconfig['Soundtracks'][existing_soundtrack] + ) self._soundtrack_name = existing_soundtrack self._existing_soundtrack_name = existing_soundtrack self._last_edited_song_type = None @@ -82,37 +109,41 @@ class SoundtrackEditWindow(ba.Window): # otherwise they can pass info on an in-progress edit self._soundtrack = existing_soundtrack['soundtrack'] self._soundtrack_name = existing_soundtrack['name'] - self._existing_soundtrack_name = ( - existing_soundtrack['existing_name']) - self._last_edited_song_type = ( - existing_soundtrack['last_edited_song_type']) + self._existing_soundtrack_name = existing_soundtrack[ + 'existing_name' + ] + self._last_edited_song_type = existing_soundtrack[ + 'last_edited_song_type' + ] else: self._soundtrack_name = None self._existing_soundtrack_name = None self._soundtrack = {} self._last_edited_song_type = None - ba.textwidget(parent=self._root_widget, - text=ba.Lstr(resource=self._r + '.nameText'), - maxwidth=80, - scale=0.8, - position=(105 + x_inset, v + 19), - color=(0.8, 0.8, 0.8, 0.5), - size=(0, 0), - h_align='right', - v_align='center') + ba.textwidget( + parent=self._root_widget, + text=ba.Lstr(resource=self._r + '.nameText'), + maxwidth=80, + scale=0.8, + position=(105 + x_inset, v + 19), + color=(0.8, 0.8, 0.8, 0.5), + size=(0, 0), + h_align='right', + v_align='center', + ) # if there's no initial value, find a good initial unused name if existing_soundtrack is None: i = 1 - st_name_text = ba.Lstr(resource=self._r + - '.newSoundtrackNameText').evaluate() + st_name_text = ba.Lstr( + resource=self._r + '.newSoundtrackNameText' + ).evaluate() if '${COUNT}' not in st_name_text: # make sure we insert number *somewhere* st_name_text = st_name_text + ' ${COUNT}' while True: - self._soundtrack_name = st_name_text.replace( - '${COUNT}', str(i)) + self._soundtrack_name = st_name_text.replace('${COUNT}', str(i)) if self._soundtrack_name not in appconfig['Soundtracks']: break i += 1 @@ -129,7 +160,8 @@ class SoundtrackEditWindow(ba.Window): description=ba.Lstr(resource=self._r + '.nameText'), editable=True, padding=4, - on_return_press_call=self._do_it_with_sound) + on_return_press_call=self._do_it_with_sound, + ) scroll_height = self._height - 180 self._scrollwidget = scrollwidget = ba.scrollwidget( @@ -140,12 +172,15 @@ class SoundtrackEditWindow(ba.Window): simple_culling_v=10, claims_left_right=True, claims_tab=True, - selection_loops_to_parent=True) + selection_loops_to_parent=True, + ) ba.widget(edit=self._text_field, down_widget=self._scrollwidget) - self._col = ba.columnwidget(parent=scrollwidget, - claims_left_right=True, - claims_tab=True, - selection_loops_to_parent=True) + self._col = ba.columnwidget( + parent=scrollwidget, + claims_left_right=True, + claims_tab=True, + selection_loops_to_parent=True, + ) self._song_type_buttons: dict[str, ba.Widget] = {} self._refresh() @@ -189,20 +224,24 @@ class SoundtrackEditWindow(ba.Window): prev_test_button: ba.Widget | None = None for index, song_type in enumerate(types): - row = ba.rowwidget(parent=self._col, - size=(self._width - 40, 40), - claims_left_right=True, - claims_tab=True, - selection_loops_to_parent=True) + row = ba.rowwidget( + parent=self._col, + size=(self._width - 40, 40), + claims_left_right=True, + claims_tab=True, + selection_loops_to_parent=True, + ) type_name = type_names_translated.get(song_type, song_type) - ba.textwidget(parent=row, - size=(230, 25), - always_highlight=True, - text=type_name, - scale=0.7, - h_align='left', - v_align='center', - maxwidth=190) + ba.textwidget( + parent=row, + size=(230, 25), + always_highlight=True, + text=type_name, + scale=0.7, + h_align='left', + v_align='center', + maxwidth=190, + ) if song_type in self._soundtrack: entry = self._soundtrack[song_type] @@ -219,34 +258,48 @@ class SoundtrackEditWindow(ba.Window): size=(230, 32), label=self._get_entry_button_display_name(entry), text_scale=0.6, - on_activate_call=ba.Call(self._get_entry, song_type, entry, - type_name), - icon=(self._file_tex if icon_type == 'file' else - self._folder_tex if icon_type == 'folder' else None), - icon_color=(1.1, 0.8, 0.2) if icon_type == 'folder' else - (1, 1, 1), + on_activate_call=ba.Call( + self._get_entry, song_type, entry, type_name + ), + icon=( + self._file_tex + if icon_type == 'file' + else self._folder_tex + if icon_type == 'folder' + else None + ), + icon_color=(1.1, 0.8, 0.2) + if icon_type == 'folder' + else (1, 1, 1), left_widget=self._text_field, iconscale=0.7, autoselect=True, - up_widget=prev_type_button) + up_widget=prev_type_button, + ) if index == 0: ba.widget(edit=btn, up_widget=self._text_field) ba.widget(edit=btn, down_widget=btn) - if (self._last_edited_song_type is not None - and song_type == self._last_edited_song_type): - ba.containerwidget(edit=row, - selected_child=btn, - visible_child=btn) - ba.containerwidget(edit=self._col, - selected_child=row, - visible_child=row) - ba.containerwidget(edit=self._scrollwidget, - selected_child=self._col, - visible_child=self._col) - ba.containerwidget(edit=self._root_widget, - selected_child=self._scrollwidget, - visible_child=self._scrollwidget) + if ( + self._last_edited_song_type is not None + and song_type == self._last_edited_song_type + ): + ba.containerwidget( + edit=row, selected_child=btn, visible_child=btn + ) + ba.containerwidget( + edit=self._col, selected_child=row, visible_child=row + ) + ba.containerwidget( + edit=self._scrollwidget, + selected_child=self._col, + visible_child=self._col, + ) + ba.containerwidget( + edit=self._root_widget, + selected_child=self._scrollwidget, + visible_child=self._scrollwidget, + ) if prev_type_button is not None: ba.widget(edit=prev_type_button, down_widget=btn) @@ -259,21 +312,25 @@ class SoundtrackEditWindow(ba.Window): text_scale=0.6, on_activate_call=ba.Call(self._test, ba.MusicType(song_type)), up_widget=prev_test_button - if prev_test_button is not None else self._text_field) + if prev_test_button is not None + else self._text_field, + ) if prev_test_button is not None: ba.widget(edit=prev_test_button, down_widget=btn) ba.widget(edit=btn, down_widget=btn, right_widget=btn) prev_test_button = btn @classmethod - def _restore_editor(cls, state: dict[str, Any], musictype: str, - entry: Any) -> None: + def _restore_editor( + cls, state: dict[str, Any], musictype: str, entry: Any + ) -> None: music = ba.app.music # Apply the change and recreate the window. soundtrack = state['soundtrack'] - existing_entry = (None if musictype not in soundtrack else - soundtrack[musictype]) + existing_entry = ( + None if musictype not in soundtrack else soundtrack[musictype] + ) if existing_entry != entry: ba.playsound(ba.getsound('gunCocking')) @@ -290,10 +347,12 @@ class SoundtrackEditWindow(ba.Window): soundtrack[musictype] = entry ba.app.ui.set_main_menu_window( - cls(state, transition='in_left').get_root_widget()) + cls(state, transition='in_left').get_root_widget() + ) - def _get_entry(self, song_type: str, entry: Any, - selection_target_name: str) -> None: + def _get_entry( + self, song_type: str, entry: Any, selection_target_name: str + ) -> None: music = ba.app.music if selection_target_name != '': selection_target_name = "'" + selection_target_name + "'" @@ -301,12 +360,18 @@ class SoundtrackEditWindow(ba.Window): 'name': self._soundtrack_name, 'existing_name': self._existing_soundtrack_name, 'soundtrack': self._soundtrack, - 'last_edited_song_type': song_type + 'last_edited_song_type': song_type, } ba.containerwidget(edit=self._root_widget, transition='out_left') - ba.app.ui.set_main_menu_window(music.get_music_player().select_entry( - ba.Call(self._restore_editor, state, song_type), entry, - selection_target_name).get_root_widget()) + ba.app.ui.set_main_menu_window( + music.get_music_player() + .select_entry( + ba.Call(self._restore_editor, state, song_type), + entry, + selection_target_name, + ) + .get_root_widget() + ) def _test(self, song_type: ba.MusicType) -> None: music = ba.app.music @@ -314,13 +379,16 @@ class SoundtrackEditWindow(ba.Window): # Warn if volume is zero. if ba.app.config.resolve('Music Volume') < 0.01: ba.playsound(ba.getsound('error')) - ba.screenmessage(ba.Lstr(resource=self._r + - '.musicVolumeZeroWarning'), - color=(1, 0.5, 0)) + ba.screenmessage( + ba.Lstr(resource=self._r + '.musicVolumeZeroWarning'), + color=(1, 0.5, 0), + ) music.set_music_play_mode(ba.MusicPlayMode.TEST) - music.do_play_music(song_type, - mode=ba.MusicPlayMode.TEST, - testsoundtrack=self._soundtrack) + music.do_play_music( + song_type, + mode=ba.MusicPlayMode.TEST, + testsoundtrack=self._soundtrack, + ) def _get_entry_button_display_name(self, entry: Any) -> str | ba.Lstr: music = ba.app.music @@ -345,33 +413,40 @@ class SoundtrackEditWindow(ba.Window): def _cancel(self) -> None: from bastd.ui.soundtrack import browser as stb + music = ba.app.music # Resets music back to normal. music.set_music_play_mode(ba.MusicPlayMode.REGULAR) ba.containerwidget(edit=self._root_widget, transition='out_right') ba.app.ui.set_main_menu_window( - stb.SoundtrackBrowserWindow( - transition='in_left').get_root_widget()) + stb.SoundtrackBrowserWindow(transition='in_left').get_root_widget() + ) def _do_it(self) -> None: from bastd.ui.soundtrack import browser as stb + music = ba.app.music cfg = ba.app.config new_name = cast(str, ba.textwidget(query=self._text_field)) - if (new_name != self._soundtrack_name - and new_name in cfg['Soundtracks']): + if new_name != self._soundtrack_name and new_name in cfg['Soundtracks']: ba.screenmessage( - ba.Lstr(resource=self._r + '.cantSaveAlreadyExistsText')) + ba.Lstr(resource=self._r + '.cantSaveAlreadyExistsText') + ) ba.playsound(ba.getsound('error')) return if not new_name: ba.playsound(ba.getsound('error')) return - if new_name == ba.Lstr(resource=self._r + - '.defaultSoundtrackNameText').evaluate(): + if ( + new_name + == ba.Lstr( + resource=self._r + '.defaultSoundtrackNameText' + ).evaluate() + ): ba.screenmessage( - ba.Lstr(resource=self._r + '.cantOverwriteDefaultText')) + ba.Lstr(resource=self._r + '.cantOverwriteDefaultText') + ) ba.playsound(ba.getsound('error')) return @@ -380,8 +455,10 @@ class SoundtrackEditWindow(ba.Window): cfg['Soundtracks'] = {} # If we had an old one, delete it. - if (self._existing_soundtrack_name is not None - and self._existing_soundtrack_name in cfg['Soundtracks']): + if ( + self._existing_soundtrack_name is not None + and self._existing_soundtrack_name in cfg['Soundtracks'] + ): del cfg['Soundtracks'][self._existing_soundtrack_name] cfg['Soundtracks'][new_name] = self._soundtrack cfg['Soundtrack'] = new_name @@ -394,8 +471,8 @@ class SoundtrackEditWindow(ba.Window): music.set_music_play_mode(ba.MusicPlayMode.REGULAR, force_restart=True) ba.app.ui.set_main_menu_window( - stb.SoundtrackBrowserWindow( - transition='in_left').get_root_widget()) + stb.SoundtrackBrowserWindow(transition='in_left').get_root_widget() + ) def _do_it_with_sound(self) -> None: ba.playsound(ba.getsound('swish')) diff --git a/dist/ba_data/python/bastd/ui/soundtrack/entrytypeselect.py b/dist/ba_data/python/bastd/ui/soundtrack/entrytypeselect.py index e009b25..21e6137 100644 --- a/dist/ba_data/python/bastd/ui/soundtrack/entrytypeselect.py +++ b/dist/ba_data/python/bastd/ui/soundtrack/entrytypeselect.py @@ -16,11 +16,13 @@ if TYPE_CHECKING: class SoundtrackEntryTypeSelectWindow(ba.Window): """Window for selecting a soundtrack entry type.""" - def __init__(self, - callback: Callable[[Any], Any], - current_entry: Any, - selection_target_name: str, - transition: str = 'in_right'): + def __init__( + self, + callback: Callable[[Any], Any], + current_entry: Any, + selection_target_name: str, + transition: str = 'in_right', + ): music = ba.app.music self._r = 'editSoundtrackWindow' @@ -35,7 +37,8 @@ class SoundtrackEntryTypeSelectWindow(ba.Window): # themselves here. do_default = True do_mac_music_app_playlist = music.supports_soundtrack_entry_type( - 'iTunesPlaylist') + 'iTunesPlaylist' + ) do_music_file = music.supports_soundtrack_entry_type('musicFile') do_music_folder = music.supports_soundtrack_entry_type('musicFolder') @@ -53,50 +56,65 @@ class SoundtrackEntryTypeSelectWindow(ba.Window): # standard UI-cleanup-check complains that something is holding on # to our instance after its ui is gone. Should restructure in a # cleaner way, but just disabling that check for now. - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height), - transition=transition, - scale=(1.7 if uiscale is ba.UIScale.SMALL else - 1.4 if uiscale is ba.UIScale.MEDIUM else 1.0)), - cleanupcheck=False) - btn = ba.buttonwidget(parent=self._root_widget, - position=(35, self._height - 65), - size=(160, 60), - scale=0.8, - text_scale=1.2, - label=ba.Lstr(resource='cancelText'), - on_activate_call=self._on_cancel_press) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height), + transition=transition, + scale=( + 1.7 + if uiscale is ba.UIScale.SMALL + else 1.4 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + ), + cleanupcheck=False, + ) + btn = ba.buttonwidget( + parent=self._root_widget, + position=(35, self._height - 65), + size=(160, 60), + scale=0.8, + text_scale=1.2, + label=ba.Lstr(resource='cancelText'), + on_activate_call=self._on_cancel_press, + ) ba.containerwidget(edit=self._root_widget, cancel_button=btn) - ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, self._height - 32), - size=(0, 0), - text=ba.Lstr(resource=self._r + '.selectASourceText'), - color=ba.app.ui.title_color, - maxwidth=230, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, self._height - 32), + size=(0, 0), + text=ba.Lstr(resource=self._r + '.selectASourceText'), + color=ba.app.ui.title_color, + maxwidth=230, + h_align='center', + v_align='center', + ) - ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, self._height - 56), - size=(0, 0), - text=selection_target_name, - color=ba.app.ui.infotextcolor, - scale=0.7, - maxwidth=230, - h_align='center', - v_align='center') + ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, self._height - 56), + size=(0, 0), + text=selection_target_name, + color=ba.app.ui.infotextcolor, + scale=0.7, + maxwidth=230, + h_align='center', + v_align='center', + ) v = self._height - 155 current_entry_type = music.get_soundtrack_entry_type(current_entry) if do_default: - btn = ba.buttonwidget(parent=self._root_widget, - size=(self._width - 100, 60), - position=(50, v), - label=ba.Lstr(resource=self._r + - '.useDefaultGameMusicText'), - on_activate_call=self._on_default_press) + btn = ba.buttonwidget( + parent=self._root_widget, + size=(self._width - 100, 60), + position=(50, v), + label=ba.Lstr(resource=self._r + '.useDefaultGameMusicText'), + on_activate_call=self._on_default_press, + ) if current_entry_type == 'default': ba.containerwidget(edit=self._root_widget, selected_child=btn) v -= spacing @@ -108,32 +126,35 @@ class SoundtrackEntryTypeSelectWindow(ba.Window): position=(50, v), label=ba.Lstr(resource=self._r + '.useITunesPlaylistText'), on_activate_call=self._on_mac_music_app_playlist_press, - icon=None) + icon=None, + ) if current_entry_type == 'iTunesPlaylist': ba.containerwidget(edit=self._root_widget, selected_child=btn) v -= spacing if do_music_file: - btn = ba.buttonwidget(parent=self._root_widget, - size=(self._width - 100, 60), - position=(50, v), - label=ba.Lstr(resource=self._r + - '.useMusicFileText'), - on_activate_call=self._on_music_file_press, - icon=ba.gettexture('file')) + btn = ba.buttonwidget( + parent=self._root_widget, + size=(self._width - 100, 60), + position=(50, v), + label=ba.Lstr(resource=self._r + '.useMusicFileText'), + on_activate_call=self._on_music_file_press, + icon=ba.gettexture('file'), + ) if current_entry_type == 'musicFile': ba.containerwidget(edit=self._root_widget, selected_child=btn) v -= spacing if do_music_folder: - btn = ba.buttonwidget(parent=self._root_widget, - size=(self._width - 100, 60), - position=(50, v), - label=ba.Lstr(resource=self._r + - '.useMusicFolderText'), - on_activate_call=self._on_music_folder_press, - icon=ba.gettexture('folder'), - icon_color=(1.1, 0.8, 0.2)) + btn = ba.buttonwidget( + parent=self._root_widget, + size=(self._width - 100, 60), + position=(50, v), + label=ba.Lstr(resource=self._r + '.useMusicFolderText'), + on_activate_call=self._on_music_folder_press, + icon=ba.gettexture('folder'), + icon_color=(1.1, 0.8, 0.2), + ) if current_entry_type == 'musicFolder': ba.containerwidget(edit=self._root_widget, selected_child=btn) v -= spacing @@ -141,24 +162,31 @@ class SoundtrackEntryTypeSelectWindow(ba.Window): def _on_mac_music_app_playlist_press(self) -> None: music = ba.app.music from bastd.ui.soundtrack.macmusicapp import ( - MacMusicAppPlaylistSelectWindow) + MacMusicAppPlaylistSelectWindow, + ) + ba.containerwidget(edit=self._root_widget, transition='out_left') current_playlist_entry: str | None - if (music.get_soundtrack_entry_type( - self._current_entry) == 'iTunesPlaylist'): + if ( + music.get_soundtrack_entry_type(self._current_entry) + == 'iTunesPlaylist' + ): current_playlist_entry = music.get_soundtrack_entry_name( - self._current_entry) + self._current_entry + ) else: current_playlist_entry = None ba.app.ui.set_main_menu_window( MacMusicAppPlaylistSelectWindow( - self._callback, current_playlist_entry, - self._current_entry).get_root_widget()) + self._callback, current_playlist_entry, self._current_entry + ).get_root_widget() + ) def _on_music_file_press(self) -> None: from ba.osmusic import OSMusicPlayer from bastd.ui.fileselector import FileSelectorWindow + ba.containerwidget(edit=self._root_widget, transition='out_left') base_path = ba.internal.android_get_external_files_dir() ba.app.ui.set_main_menu_window( @@ -167,19 +195,26 @@ class SoundtrackEntryTypeSelectWindow(ba.Window): callback=self._music_file_selector_cb, show_base_path=False, valid_file_extensions=( - OSMusicPlayer.get_valid_music_file_extensions()), - allow_folders=False).get_root_widget()) + OSMusicPlayer.get_valid_music_file_extensions() + ), + allow_folders=False, + ).get_root_widget() + ) def _on_music_folder_press(self) -> None: from bastd.ui.fileselector import FileSelectorWindow + ba.containerwidget(edit=self._root_widget, transition='out_left') base_path = ba.internal.android_get_external_files_dir() ba.app.ui.set_main_menu_window( - FileSelectorWindow(base_path, - callback=self._music_folder_selector_cb, - show_base_path=False, - valid_file_extensions=[], - allow_folders=True).get_root_widget()) + FileSelectorWindow( + base_path, + callback=self._music_folder_selector_cb, + show_base_path=False, + valid_file_extensions=[], + allow_folders=True, + ).get_root_widget() + ) def _music_file_selector_cb(self, result: str | None) -> None: if result is None: diff --git a/dist/ba_data/python/bastd/ui/soundtrack/macmusicapp.py b/dist/ba_data/python/bastd/ui/soundtrack/macmusicapp.py index d5b030a..c564018 100644 --- a/dist/ba_data/python/bastd/ui/soundtrack/macmusicapp.py +++ b/dist/ba_data/python/bastd/ui/soundtrack/macmusicapp.py @@ -16,9 +16,14 @@ if TYPE_CHECKING: class MacMusicAppPlaylistSelectWindow(ba.Window): """Window for selecting an iTunes playlist.""" - def __init__(self, callback: Callable[[Any], Any], - existing_playlist: str | None, existing_entry: Any): + def __init__( + self, + callback: Callable[[Any], Any], + existing_playlist: str | None, + existing_entry: Any, + ): from ba.macmusicapp import MacMusicAppMusicPlayer + self._r = 'editSoundtrackWindow' self._callback = callback self._existing_playlist = existing_playlist @@ -28,63 +33,78 @@ class MacMusicAppPlaylistSelectWindow(ba.Window): self._spacing = 45.0 v = self._height - 90.0 v -= self._spacing * 1.0 - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height), transition='in_right')) - btn = ba.buttonwidget(parent=self._root_widget, - position=(35, self._height - 65), - size=(130, 50), - label=ba.Lstr(resource='cancelText'), - on_activate_call=self._back, - autoselect=True) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height), transition='in_right' + ) + ) + btn = ba.buttonwidget( + parent=self._root_widget, + position=(35, self._height - 65), + size=(130, 50), + label=ba.Lstr(resource='cancelText'), + on_activate_call=self._back, + autoselect=True, + ) ba.containerwidget(edit=self._root_widget, cancel_button=btn) - ba.textwidget(parent=self._root_widget, - position=(20, self._height - 54), - size=(self._width, 25), - text=ba.Lstr(resource=self._r + '.selectAPlaylistText'), - color=ba.app.ui.title_color, - h_align='center', - v_align='center', - maxwidth=200) - self._scrollwidget = ba.scrollwidget(parent=self._root_widget, - position=(40, v - 340), - size=(self._width - 80, 400), - claims_tab=True, - selection_loops_to_parent=True) + ba.textwidget( + parent=self._root_widget, + position=(20, self._height - 54), + size=(self._width, 25), + text=ba.Lstr(resource=self._r + '.selectAPlaylistText'), + color=ba.app.ui.title_color, + h_align='center', + v_align='center', + maxwidth=200, + ) + self._scrollwidget = ba.scrollwidget( + parent=self._root_widget, + position=(40, v - 340), + size=(self._width - 80, 400), + claims_tab=True, + selection_loops_to_parent=True, + ) ba.widget(edit=self._scrollwidget, right_widget=self._scrollwidget) - self._column = ba.columnwidget(parent=self._scrollwidget, - claims_tab=True, - selection_loops_to_parent=True) + self._column = ba.columnwidget( + parent=self._scrollwidget, + claims_tab=True, + selection_loops_to_parent=True, + ) - ba.textwidget(parent=self._column, - size=(self._width - 80, 22), - text=ba.Lstr(resource=self._r + '.fetchingITunesText'), - color=(0.6, 0.9, 0.6, 1.0), - scale=0.8) + ba.textwidget( + parent=self._column, + size=(self._width - 80, 22), + text=ba.Lstr(resource=self._r + '.fetchingITunesText'), + color=(0.6, 0.9, 0.6, 1.0), + scale=0.8, + ) musicplayer = ba.app.music.get_music_player() assert isinstance(musicplayer, MacMusicAppMusicPlayer) musicplayer.get_playlists(self._playlists_cb) - ba.containerwidget(edit=self._root_widget, - selected_child=self._scrollwidget) + ba.containerwidget( + edit=self._root_widget, selected_child=self._scrollwidget + ) def _playlists_cb(self, playlists: list[str]) -> None: if self._column: for widget in self._column.get_children(): widget.delete() for i, playlist in enumerate(playlists): - txt = ba.textwidget(parent=self._column, - size=(self._width - 80, 30), - text=playlist, - v_align='center', - maxwidth=self._width - 110, - selectable=True, - on_activate_call=ba.Call( - self._sel, playlist), - click_activate=True) + txt = ba.textwidget( + parent=self._column, + size=(self._width - 80, 30), + text=playlist, + v_align='center', + maxwidth=self._width - 110, + selectable=True, + on_activate_call=ba.Call(self._sel, playlist), + click_activate=True, + ) ba.widget(edit=txt, show_buffer_top=40, show_buffer_bottom=40) if playlist == self._existing_playlist: - ba.columnwidget(edit=self._column, - selected_child=txt, - visible_child=txt) + ba.columnwidget( + edit=self._column, selected_child=txt, visible_child=txt + ) if i == len(playlists) - 1: ba.widget(edit=txt, down_widget=txt) diff --git a/dist/ba_data/python/bastd/ui/specialoffer.py b/dist/ba_data/python/bastd/ui/specialoffer.py index 1935f3b..b2f864a 100644 --- a/dist/ba_data/python/bastd/ui/specialoffer.py +++ b/dist/ba_data/python/bastd/ui/specialoffer.py @@ -21,9 +21,10 @@ class SpecialOfferWindow(ba.Window): # pylint: disable=too-many-statements # pylint: disable=too-many-branches # pylint: disable=too-many-locals - from ba.internal import (get_store_item_display_size, get_clean_price) + from ba.internal import get_store_item_display_size, get_clean_price from ba import SpecialChar from bastd.ui.store import item as storeitemui + self._cancel_delay = offer.get('cancelDelay', 0) # First thing: if we're offering pro or an IAP, see if we have a @@ -35,8 +36,9 @@ class SpecialOfferWindow(ba.Window): # Misnomer: 'pro' actually means offer 'pro_sale'. if offer['item'] in ['pro', 'pro_fullprice']: - real_price = ba.internal.get_price('pro' if offer['item'] == - 'pro_fullprice' else 'pro_sale') + real_price = ba.internal.get_price( + 'pro' if offer['item'] == 'pro_fullprice' else 'pro_sale' + ) if real_price is None and ba.app.debug_build: print('NOTE: Faking prices for debug build.') real_price = '$1.23' @@ -67,19 +69,31 @@ class SpecialOfferWindow(ba.Window): ba.internal.lock_all_input() ba.timer(1.0, ba.internal.unlock_all_input, timetype=ba.TimeType.REAL) ba.playsound(ba.getsound('ding')) - ba.timer(0.3, - lambda: ba.playsound(ba.getsound('ooh')), - timetype=ba.TimeType.REAL) + ba.timer( + 0.3, + lambda: ba.playsound(ba.getsound('ooh')), + timetype=ba.TimeType.REAL, + ) self._offer = copy.deepcopy(offer) self._width = 580 self._height = 590 uiscale = ba.app.ui.uiscale - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height), - transition=transition, - scale=(1.2 if uiscale is ba.UIScale.SMALL else - 1.15 if uiscale is ba.UIScale.MEDIUM else 1.0), - stack_offset=(0, -15) if uiscale is ba.UIScale.SMALL else (0, 0))) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height), + transition=transition, + scale=( + 1.2 + if uiscale is ba.UIScale.SMALL + else 1.15 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + stack_offset=(0, -15) + if uiscale is ba.UIScale.SMALL + else (0, 0), + ) + ) self._is_bundle_sale = False try: if offer['item'] in ['pro', 'pro_fullprice']: @@ -92,11 +106,14 @@ class SpecialOfferWindow(ba.Window): percent_off_text = '' else: # If the offer includes bonus tickets, it's a bundle-sale. - if ('bonusTickets' in offer - and offer['bonusTickets'] is not None): + if ( + 'bonusTickets' in offer + and offer['bonusTickets'] is not None + ): self._is_bundle_sale = True original_price = ba.internal.get_v1_account_misc_read_val( - 'price.' + self._offer_item, 9999) + 'price.' + self._offer_item, 9999 + ) # For pure ticket prices we can show a percent-off. if isinstance(offer['price'], int): @@ -105,11 +122,13 @@ class SpecialOfferWindow(ba.Window): original_price_str = tchar + str(original_price) new_price_str = tchar + str(new_price) percent_off = int( - round(100.0 - - (float(new_price) / original_price) * 100.0)) + round( + 100.0 - (float(new_price) / original_price) * 100.0 + ) + ) percent_off_text = ' ' + ba.Lstr( - resource='store.salePercentText').evaluate().replace( - '${PERCENT}', str(percent_off)) + resource='store.salePercentText' + ).evaluate().replace('${PERCENT}', str(percent_off)) else: original_price_str = new_price_str = '?' percent_off_text = '' @@ -122,41 +141,51 @@ class SpecialOfferWindow(ba.Window): # If its a bundle sale, change the title. if self._is_bundle_sale: - sale_text = ba.Lstr(resource='store.saleBundleText', - fallback_resource='store.saleText').evaluate() + sale_text = ba.Lstr( + resource='store.saleBundleText', + fallback_resource='store.saleText', + ).evaluate() else: # For full pro we say 'Upgrade?' since its not really a sale. if offer['item'] == 'pro_fullprice': sale_text = ba.Lstr( resource='store.upgradeQuestionText', - fallback_resource='store.saleExclaimText').evaluate() + fallback_resource='store.saleExclaimText', + ).evaluate() else: sale_text = ba.Lstr( resource='store.saleExclaimText', - fallback_resource='store.saleText').evaluate() + fallback_resource='store.saleText', + ).evaluate() self._title_text = ba.textwidget( parent=self._root_widget, position=(self._width * 0.5, self._height - 40), size=(0, 0), - text=sale_text + - ((' ' + ba.Lstr(resource='store.oneTimeOnlyText').evaluate()) - if self._offer['oneTimeOnly'] else '') + percent_off_text, + text=sale_text + + ( + (' ' + ba.Lstr(resource='store.oneTimeOnlyText').evaluate()) + if self._offer['oneTimeOnly'] + else '' + ) + + percent_off_text, h_align='center', v_align='center', maxwidth=self._width * 0.9 - 220, scale=1.4, - color=(0.3, 1, 0.3)) + color=(0.3, 1, 0.3), + ) self._flash_on = False self._flashing_timer: ba.Timer | None = ba.Timer( 0.05, ba.WeakCall(self._flash_cycle), repeat=True, - timetype=ba.TimeType.REAL) - ba.timer(0.6, - ba.WeakCall(self._stop_flashing), - timetype=ba.TimeType.REAL) + timetype=ba.TimeType.REAL, + ) + ba.timer( + 0.6, ba.WeakCall(self._stop_flashing), timetype=ba.TimeType.REAL + ) size = get_store_item_display_size(self._offer_item) display: dict[str, Any] = {} @@ -164,56 +193,69 @@ class SpecialOfferWindow(ba.Window): self._offer_item, display, parent_widget=self._root_widget, - b_pos=(self._width * 0.5 - size[0] * 0.5 + 10 - - ((size[0] * 0.5 + 30) if self._is_bundle_sale else 0), - self._height * 0.5 - size[1] * 0.5 + 20 + - (20 if self._is_bundle_sale else 0)), + b_pos=( + self._width * 0.5 + - size[0] * 0.5 + + 10 + - ((size[0] * 0.5 + 30) if self._is_bundle_sale else 0), + self._height * 0.5 + - size[1] * 0.5 + + 20 + + (20 if self._is_bundle_sale else 0), + ), b_width=size[0], b_height=size[1], - button=not self._is_bundle_sale) + button=not self._is_bundle_sale, + ) # Wire up the parts we need. if self._is_bundle_sale: - self._plus_text = ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, - self._height * 0.5 + 50), - size=(0, 0), - text='+', - h_align='center', - v_align='center', - maxwidth=self._width * 0.9, - scale=1.4, - color=(0.5, 0.5, 0.5)) + self._plus_text = ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, self._height * 0.5 + 50), + size=(0, 0), + text='+', + h_align='center', + v_align='center', + maxwidth=self._width * 0.9, + scale=1.4, + color=(0.5, 0.5, 0.5), + ) self._plus_tickets = ba.textwidget( parent=self._root_widget, position=(self._width * 0.5 + 120, self._height * 0.5 + 50), size=(0, 0), - text=ba.charstr(SpecialChar.TICKET_BACKING) + - str(offer['bonusTickets']), + text=ba.charstr(SpecialChar.TICKET_BACKING) + + str(offer['bonusTickets']), h_align='center', v_align='center', maxwidth=self._width * 0.9, scale=2.5, - color=(0.2, 1, 0.2)) - self._price_text = ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, 150), - size=(0, 0), - text=real_price, - h_align='center', - v_align='center', - maxwidth=self._width * 0.9, - scale=1.4, - color=(0.2, 1, 0.2)) + color=(0.2, 1, 0.2), + ) + self._price_text = ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, 150), + size=(0, 0), + text=real_price, + h_align='center', + v_align='center', + maxwidth=self._width * 0.9, + scale=1.4, + color=(0.2, 1, 0.2), + ) # Total-value if they supplied it. total_worth_item = offer.get('valueItem', None) if total_worth_item is not None: price = ba.internal.get_price(total_worth_item) - total_worth_price = (get_clean_price(price) - if price is not None else None) + total_worth_price = ( + get_clean_price(price) if price is not None else None + ) if total_worth_price is not None: - total_worth_text = ba.Lstr(resource='store.totalWorthText', - subs=[('${TOTAL_WORTH}', - total_worth_price)]) + total_worth_text = ba.Lstr( + resource='store.totalWorthText', + subs=[('${TOTAL_WORTH}', total_worth_price)], + ) self._total_worth_text = ba.textwidget( parent=self._root_widget, text=total_worth_text, @@ -225,22 +267,27 @@ class SpecialOfferWindow(ba.Window): v_align='center', shadow=1.0, flatness=1.0, - color=(0.3, 1, 1)) + color=(0.3, 1, 1), + ) elif offer['item'] == 'pro_fullprice': # for full-price pro we simply show full price ba.textwidget(edit=display['price_widget'], text=real_price) - ba.buttonwidget(edit=display['button'], - on_activate_call=self._purchase) + ba.buttonwidget( + edit=display['button'], on_activate_call=self._purchase + ) else: # Show old/new prices otherwise (for pro sale). - ba.buttonwidget(edit=display['button'], - on_activate_call=self._purchase) + ba.buttonwidget( + edit=display['button'], on_activate_call=self._purchase + ) ba.imagewidget(edit=display['price_slash_widget'], opacity=1.0) - ba.textwidget(edit=display['price_widget_left'], - text=original_price_str) - ba.textwidget(edit=display['price_widget_right'], - text=new_price_str) + ba.textwidget( + edit=display['price_widget_left'], text=original_price_str + ) + ba.textwidget( + edit=display['price_widget_right'], text=new_price_str + ) # Add ticket button only if this is ticket-purchasable. if isinstance(offer.get('price'), int): @@ -254,41 +301,49 @@ class SpecialOfferWindow(ba.Window): textcolor=(0.2, 1, 0.2), autoselect=True, label=ba.Lstr(resource='getTicketsWindow.titleText'), - on_activate_call=self._on_get_more_tickets_press) + on_activate_call=self._on_get_more_tickets_press, + ) self._ticket_text_update_timer = ba.Timer( 1.0, ba.WeakCall(self._update_tickets_text), timetype=ba.TimeType.REAL, - repeat=True) + repeat=True, + ) self._update_tickets_text() - self._update_timer = ba.Timer(1.0, - ba.WeakCall(self._update), - timetype=ba.TimeType.REAL, - repeat=True) + self._update_timer = ba.Timer( + 1.0, + ba.WeakCall(self._update), + timetype=ba.TimeType.REAL, + repeat=True, + ) self._cancel_button = ba.buttonwidget( parent=self._root_widget, - position=(50, 40) if self._is_bundle_sale else - (self._width * 0.5 - 75, 40), + position=(50, 40) + if self._is_bundle_sale + else (self._width * 0.5 - 75, 40), size=(150, 60), scale=1.0, on_activate_call=self._cancel, autoselect=True, - label=ba.Lstr(resource='noThanksText')) + label=ba.Lstr(resource='noThanksText'), + ) self._cancel_countdown_text = ba.textwidget( parent=self._root_widget, text='', - position=(50 + 150 + 20, 40 + 27) if self._is_bundle_sale else - (self._width * 0.5 - 75 + 150 + 20, 40 + 27), + position=(50 + 150 + 20, 40 + 27) + if self._is_bundle_sale + else (self._width * 0.5 - 75 + 150 + 20, 40 + 27), scale=1.1, size=(0, 0), h_align='left', v_align='center', shadow=1.0, flatness=1.0, - color=(0.6, 0.5, 0.5)) + color=(0.6, 0.5, 0.5), + ) self._update_cancel_button_graphics() if self._is_bundle_sale: @@ -299,14 +354,19 @@ class SpecialOfferWindow(ba.Window): scale=1.0, on_activate_call=self._purchase, autoselect=True, - label=ba.Lstr(resource='store.purchaseText')) + label=ba.Lstr(resource='store.purchaseText'), + ) - ba.containerwidget(edit=self._root_widget, - cancel_button=self._cancel_button, - start_button=self._purchase_button - if self._is_bundle_sale else None, - selected_child=self._purchase_button - if self._is_bundle_sale else display['button']) + ba.containerwidget( + edit=self._root_widget, + cancel_button=self._cancel_button, + start_button=self._purchase_button + if self._is_bundle_sale + else None, + selected_child=self._purchase_button + if self._is_bundle_sale + else display['button'], + ) def _stop_flashing(self) -> None: self._flashing_timer = None @@ -316,19 +376,25 @@ class SpecialOfferWindow(ba.Window): if not self._root_widget: return self._flash_on = not self._flash_on - ba.textwidget(edit=self._title_text, - color=(0.3, 1, 0.3) if self._flash_on else (1, 0.5, 0)) + ba.textwidget( + edit=self._title_text, + color=(0.3, 1, 0.3) if self._flash_on else (1, 0.5, 0), + ) def _update_cancel_button_graphics(self) -> None: - ba.buttonwidget(edit=self._cancel_button, - color=(0.5, 0.5, 0.5) if self._cancel_delay > 0 else - (0.7, 0.4, 0.34), - textcolor=(0.5, 0.5, - 0.5) if self._cancel_delay > 0 else - (0.9, 0.9, 1.0)) + ba.buttonwidget( + edit=self._cancel_button, + color=(0.5, 0.5, 0.5) + if self._cancel_delay > 0 + else (0.7, 0.4, 0.34), + textcolor=(0.5, 0.5, 0.5) + if self._cancel_delay > 0 + else (0.9, 0.9, 1.0), + ) ba.textwidget( edit=self._cancel_countdown_text, - text=str(self._cancel_delay) if self._cancel_delay > 0 else '') + text=str(self._cancel_delay) if self._cancel_delay > 0 else '', + ) def _update(self) -> None: @@ -361,12 +427,14 @@ class SpecialOfferWindow(ba.Window): def _update_tickets_text(self) -> None: from ba import SpecialChar + if not self._root_widget: return sval: str | ba.Lstr if ba.internal.get_v1_account_state() == 'signed_in': - sval = (ba.charstr(SpecialChar.TICKET) + - str(ba.internal.get_v1_account_ticket_count())) + sval = ba.charstr(SpecialChar.TICKET) + str( + ba.internal.get_v1_account_ticket_count() + ) else: sval = ba.Lstr(resource='getTicketsWindow.titleText') ba.buttonwidget(edit=self._get_tickets_button, label=sval) @@ -374,6 +442,7 @@ class SpecialOfferWindow(ba.Window): def _on_get_more_tickets_press(self) -> None: from bastd.ui import account from bastd.ui import getcurrency + if ba.internal.get_v1_account_state() != 'signed_in': account.show_sign_in_prompt() return @@ -383,6 +452,7 @@ class SpecialOfferWindow(ba.Window): from ba.internal import get_store_item_name_translated from bastd.ui import getcurrency from bastd.ui import confirm + if self._offer['item'] == 'pro': ba.internal.purchase('pro_sale') elif self._offer['item'] == 'pro_fullprice': @@ -396,27 +466,34 @@ class SpecialOfferWindow(ba.Window): ticket_count = ba.internal.get_v1_account_ticket_count() except Exception: ticket_count = None - if (ticket_count is not None - and ticket_count < self._offer['price']): + if ticket_count is not None and ticket_count < self._offer['price']: getcurrency.show_get_tickets_prompt() ba.playsound(ba.getsound('error')) return def do_it() -> None: - ba.internal.in_game_purchase('offer:' + str(self._offer['id']), - self._offer['price']) + ba.internal.in_game_purchase( + 'offer:' + str(self._offer['id']), self._offer['price'] + ) ba.playsound(ba.getsound('swish')) - confirm.ConfirmWindow(ba.Lstr( - resource='store.purchaseConfirmText', - subs=[('${ITEM}', - get_store_item_name_translated(self._offer['item']))]), - width=400, - height=120, - action=do_it, - ok_text=ba.Lstr( - resource='store.purchaseText', - fallback_resource='okText')) + confirm.ConfirmWindow( + ba.Lstr( + resource='store.purchaseConfirmText', + subs=[ + ( + '${ITEM}', + get_store_item_name_translated(self._offer['item']), + ) + ], + ), + width=400, + height=120, + action=do_it, + ok_text=ba.Lstr( + resource='store.purchaseText', fallback_resource='okText' + ), + ) def _cancel(self) -> None: if self._cancel_delay > 0: @@ -429,14 +506,15 @@ def show_offer() -> bool: """(internal)""" try: from bastd.ui import feedback + app = ba.app # Space things out a bit so we don't hit the poor user with an ad and # then an in-game offer. has_been_long_enough_since_ad = True - if (app.ads.last_ad_completion_time is not None and - (ba.time(ba.TimeType.REAL) - app.ads.last_ad_completion_time < - 30.0)): + if app.ads.last_ad_completion_time is not None and ( + ba.time(ba.TimeType.REAL) - app.ads.last_ad_completion_time < 30.0 + ): has_been_long_enough_since_ad = False if app.special_offer is not None and has_been_long_enough_since_ad: @@ -447,7 +525,7 @@ def show_offer() -> bool: cfg = app.config cfg['pendingSpecialOffer'] = { 'a': ba.internal.get_public_login_id(), - 'o': app.special_offer + 'o': app.special_offer, } cfg.commit() diff --git a/dist/ba_data/python/bastd/ui/store/browser.py b/dist/ba_data/python/bastd/ui/store/browser.py index e2471b7..49a301c 100644 --- a/dist/ba_data/python/bastd/ui/store/browser.py +++ b/dist/ba_data/python/bastd/ui/store/browser.py @@ -22,19 +22,22 @@ class StoreBrowserWindow(ba.Window): class TabID(Enum): """Our available tab types.""" + EXTRAS = 'extras' MAPS = 'maps' MINIGAMES = 'minigames' CHARACTERS = 'characters' ICONS = 'icons' - def __init__(self, - transition: str = 'in_right', - modal: bool = False, - show_tab: StoreBrowserWindow.TabID | None = None, - on_close_call: Callable[[], Any] | None = None, - back_location: str | None = None, - origin_widget: ba.Widget | None = None): + def __init__( + self, + transition: str = 'in_right', + modal: bool = False, + show_tab: StoreBrowserWindow.TabID | None = None, + on_close_call: Callable[[], Any] | None = None, + back_location: str | None = None, + origin_widget: ba.Widget | None = None, + ): # pylint: disable=too-many-statements # pylint: disable=too-many-locals from bastd.ui.tabs import TabRow @@ -66,8 +69,13 @@ class StoreBrowserWindow(ba.Window): self._modal = modal self._width = 1240 if uiscale is ba.UIScale.SMALL else 1040 self._x_inset = x_inset = 100 if uiscale is ba.UIScale.SMALL else 0 - self._height = (578 if uiscale is ba.UIScale.SMALL else - 645 if uiscale is ba.UIScale.MEDIUM else 800) + self._height = ( + 578 + if uiscale is ba.UIScale.SMALL + else 645 + if uiscale is ba.UIScale.MEDIUM + else 800 + ) self._current_tab: StoreBrowserWindow.TabID | None = None extra_top = 30 if uiscale is ba.UIScale.SMALL else 0 @@ -75,15 +83,28 @@ class StoreBrowserWindow(ba.Window): self._r = 'store' self._last_buy_time: float | None = None - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height + extra_top), - transition=transition, - toolbar_visibility='menu_full', - scale=(1.3 if uiscale is ba.UIScale.SMALL else - 0.9 if uiscale is ba.UIScale.MEDIUM else 0.8), - scale_origin_stack_offset=scale_origin, - stack_offset=((0, -5) if uiscale is ba.UIScale.SMALL else ( - 0, 0) if uiscale is ba.UIScale.MEDIUM else (0, 0)))) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height + extra_top), + transition=transition, + toolbar_visibility='menu_full', + scale=( + 1.3 + if uiscale is ba.UIScale.SMALL + else 0.9 + if uiscale is ba.UIScale.MEDIUM + else 0.8 + ), + scale_origin_stack_offset=scale_origin, + stack_offset=( + (0, -5) + if uiscale is ba.UIScale.SMALL + else (0, 0) + if uiscale is ba.UIScale.MEDIUM + else (0, 0) + ), + ) + ) self._back_button = btn = ba.buttonwidget( parent=self._root_widget, @@ -93,7 +114,8 @@ class StoreBrowserWindow(ba.Window): autoselect=True, label=ba.Lstr(resource='doneText' if self._modal else 'backText'), button_type=None if self._modal else 'back', - on_activate_call=self._back) + on_activate_call=self._back, + ) ba.containerwidget(edit=self._root_widget, cancel_button=btn) self._ticket_count_text: ba.Widget | None = None @@ -110,13 +132,16 @@ class StoreBrowserWindow(ba.Window): left_widget=self._back_button, color=(0.7, 0.5, 0.85), textcolor=(0.2, 1.0, 0.2), - label=ba.Lstr(resource='getTicketsWindow.titleText')) + label=ba.Lstr(resource='getTicketsWindow.titleText'), + ) else: - self._ticket_count_text = ba.textwidget(parent=self._root_widget, - size=(210, 64), - color=(0.2, 1.0, 0.2), - h_align='center', - v_align='center') + self._ticket_count_text = ba.textwidget( + parent=self._root_widget, + size=(210, 64), + color=(0.2, 1.0, 0.2), + h_align='center', + v_align='center', + ) # Move this dynamically to keep it out of the way of the party icon. self._update_get_tickets_button_pos() @@ -124,15 +149,18 @@ class StoreBrowserWindow(ba.Window): 1.0, ba.WeakCall(self._update_get_tickets_button_pos), repeat=True, - timetype=ba.TimeType.REAL) + timetype=ba.TimeType.REAL, + ) if self._get_tickets_button: - ba.widget(edit=self._back_button, - right_widget=self._get_tickets_button) + ba.widget( + edit=self._back_button, right_widget=self._get_tickets_button + ) self._ticket_text_update_timer = ba.Timer( 1.0, ba.WeakCall(self._update_tickets_text), timetype=ba.TimeType.REAL, - repeat=True) + repeat=True, + ) self._update_tickets_text() app = ba.app @@ -146,24 +174,28 @@ class StoreBrowserWindow(ba.Window): color=(0.35, 0.3, 0.4), selectable=False, textcolor=(0.55, 0.5, 0.6), - label=ba.Lstr( - resource='getTicketsWindow.restorePurchasesText')) + label=ba.Lstr(resource='getTicketsWindow.restorePurchasesText'), + ) - ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, self._height - 44), - size=(0, 0), - color=app.ui.title_color, - scale=1.5, - h_align='center', - v_align='center', - text=ba.Lstr(resource='storeText'), - maxwidth=420) + ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, self._height - 44), + size=(0, 0), + color=app.ui.title_color, + scale=1.5, + h_align='center', + v_align='center', + text=ba.Lstr(resource='storeText'), + maxwidth=420, + ) if not self._modal: - ba.buttonwidget(edit=self._back_button, - button_type='backSmall', - size=(60, 60), - label=ba.charstr(SpecialChar.BACK)) + ba.buttonwidget( + edit=self._back_button, + button_type='backSmall', + size=(60, 60), + label=ba.charstr(SpecialChar.BACK), + ) scroll_buffer_h = 130 + 2 * x_inset tab_buffer_h = 250 + 2 * x_inset @@ -171,21 +203,28 @@ class StoreBrowserWindow(ba.Window): tabs_def = [ (self.TabID.EXTRAS, ba.Lstr(resource=self._r + '.extrasText')), (self.TabID.MAPS, ba.Lstr(resource=self._r + '.mapsText')), - (self.TabID.MINIGAMES, - ba.Lstr(resource=self._r + '.miniGamesText')), - (self.TabID.CHARACTERS, - ba.Lstr(resource=self._r + '.charactersText')), + ( + self.TabID.MINIGAMES, + ba.Lstr(resource=self._r + '.miniGamesText'), + ), + ( + self.TabID.CHARACTERS, + ba.Lstr(resource=self._r + '.charactersText'), + ), (self.TabID.ICONS, ba.Lstr(resource=self._r + '.iconsText')), ] - self._tab_row = TabRow(self._root_widget, - tabs_def, - pos=(tab_buffer_h * 0.5, self._height - 130), - size=(self._width - tab_buffer_h, 50), - on_select_call=self._set_tab) + self._tab_row = TabRow( + self._root_widget, + tabs_def, + pos=(tab_buffer_h * 0.5, self._height - 130), + size=(self._width - tab_buffer_h, 50), + on_select_call=self._set_tab, + ) - self._purchasable_count_widgets: dict[StoreBrowserWindow.TabID, - dict[str, Any]] = {} + self._purchasable_count_widgets: dict[ + StoreBrowserWindow.TabID, dict[str, Any] + ] = {} # Create our purchasable-items tags and have them update over time. for tab_id, tab in self._tab_row.tabs.items(): @@ -194,73 +233,84 @@ class StoreBrowserWindow(ba.Window): button = tab.button rad = 10 center = (pos[0] + 0.1 * size[0], pos[1] + 0.9 * size[1]) - img = ba.imagewidget(parent=self._root_widget, - position=(center[0] - rad * 1.04, - center[1] - rad * 1.15), - size=(rad * 2.2, rad * 2.2), - texture=ba.gettexture('circleShadow'), - color=(1, 0, 0)) - txt = ba.textwidget(parent=self._root_widget, - position=center, - size=(0, 0), - h_align='center', - v_align='center', - maxwidth=1.4 * rad, - scale=0.6, - shadow=1.0, - flatness=1.0) + img = ba.imagewidget( + parent=self._root_widget, + position=(center[0] - rad * 1.04, center[1] - rad * 1.15), + size=(rad * 2.2, rad * 2.2), + texture=ba.gettexture('circleShadow'), + color=(1, 0, 0), + ) + txt = ba.textwidget( + parent=self._root_widget, + position=center, + size=(0, 0), + h_align='center', + v_align='center', + maxwidth=1.4 * rad, + scale=0.6, + shadow=1.0, + flatness=1.0, + ) rad = 20 - sale_img = ba.imagewidget(parent=self._root_widget, - position=(center[0] - rad, - center[1] - rad), - size=(rad * 2, rad * 2), - draw_controller=button, - texture=ba.gettexture('circleZigZag'), - color=(0.5, 0, 1.0)) - sale_title_text = ba.textwidget(parent=self._root_widget, - position=(center[0], - center[1] + 0.24 * rad), - size=(0, 0), - h_align='center', - v_align='center', - draw_controller=button, - maxwidth=1.4 * rad, - scale=0.6, - shadow=0.0, - flatness=1.0, - color=(0, 1, 0)) - sale_time_text = ba.textwidget(parent=self._root_widget, - position=(center[0], - center[1] - 0.29 * rad), - size=(0, 0), - h_align='center', - v_align='center', - draw_controller=button, - maxwidth=1.4 * rad, - scale=0.4, - shadow=0.0, - flatness=1.0, - color=(0, 1, 0)) + sale_img = ba.imagewidget( + parent=self._root_widget, + position=(center[0] - rad, center[1] - rad), + size=(rad * 2, rad * 2), + draw_controller=button, + texture=ba.gettexture('circleZigZag'), + color=(0.5, 0, 1.0), + ) + sale_title_text = ba.textwidget( + parent=self._root_widget, + position=(center[0], center[1] + 0.24 * rad), + size=(0, 0), + h_align='center', + v_align='center', + draw_controller=button, + maxwidth=1.4 * rad, + scale=0.6, + shadow=0.0, + flatness=1.0, + color=(0, 1, 0), + ) + sale_time_text = ba.textwidget( + parent=self._root_widget, + position=(center[0], center[1] - 0.29 * rad), + size=(0, 0), + h_align='center', + v_align='center', + draw_controller=button, + maxwidth=1.4 * rad, + scale=0.4, + shadow=0.0, + flatness=1.0, + color=(0, 1, 0), + ) self._purchasable_count_widgets[tab_id] = { 'img': img, 'text': txt, 'sale_img': sale_img, 'sale_title_text': sale_title_text, - 'sale_time_text': sale_time_text + 'sale_time_text': sale_time_text, } - self._tab_update_timer = ba.Timer(1.0, - ba.WeakCall(self._update_tabs), - timetype=ba.TimeType.REAL, - repeat=True) + self._tab_update_timer = ba.Timer( + 1.0, + ba.WeakCall(self._update_tabs), + timetype=ba.TimeType.REAL, + repeat=True, + ) self._update_tabs() if self._get_tickets_button: last_tab_button = self._tab_row.tabs[tabs_def[-1][0]].button - ba.widget(edit=self._get_tickets_button, - down_widget=last_tab_button) - ba.widget(edit=last_tab_button, - up_widget=self._get_tickets_button, - right_widget=self._get_tickets_button) + ba.widget( + edit=self._get_tickets_button, down_widget=last_tab_button + ) + ba.widget( + edit=last_tab_button, + up_widget=self._get_tickets_button, + right_widget=self._get_tickets_button, + ) self._scroll_width = self._width - scroll_buffer_h self._scroll_height = self._height - 180 @@ -271,11 +321,20 @@ class StoreBrowserWindow(ba.Window): def _update_get_tickets_button_pos(self) -> None: uiscale = ba.app.ui.uiscale - pos = (self._width - 252 - - (self._x_inset + - (47 if uiscale is ba.UIScale.SMALL - and ba.internal.is_party_icon_visible() else 0)), - self._height - 70) + pos = ( + self._width + - 252 + - ( + self._x_inset + + ( + 47 + if uiscale is ba.UIScale.SMALL + and ba.internal.is_party_icon_visible() + else 0 + ) + ), + self._height - 70, + ) if self._get_tickets_button: ba.buttonwidget(edit=self._get_tickets_button, position=pos) if self._ticket_count_text: @@ -283,27 +342,36 @@ class StoreBrowserWindow(ba.Window): def _restore_purchases(self) -> None: from bastd.ui import account + if ba.internal.get_v1_account_state() != 'signed_in': account.show_sign_in_prompt() else: ba.internal.restore_purchases() def _update_tabs(self) -> None: - from ba.internal import (get_available_sale_time, - get_available_purchase_count) + from ba.internal import ( + get_available_sale_time, + get_available_purchase_count, + ) + if not self._root_widget: return for tab_id, tab_data in list(self._purchasable_count_widgets.items()): sale_time = get_available_sale_time(tab_id.value) if sale_time is not None: - ba.textwidget(edit=tab_data['sale_title_text'], - text=ba.Lstr(resource='store.saleText')) - ba.textwidget(edit=tab_data['sale_time_text'], - text=ba.timestring( - sale_time, - centi=False, - timeformat=ba.TimeFormat.MILLISECONDS)) + ba.textwidget( + edit=tab_data['sale_title_text'], + text=ba.Lstr(resource='store.saleText'), + ) + ba.textwidget( + edit=tab_data['sale_time_text'], + text=ba.timestring( + sale_time, + centi=False, + timeformat=ba.TimeFormat.MILLISECONDS, + ), + ) ba.imagewidget(edit=tab_data['sale_img'], opacity=1.0) count = 0 else: @@ -321,12 +389,14 @@ class StoreBrowserWindow(ba.Window): def _update_tickets_text(self) -> None: from ba import SpecialChar + if not self._root_widget: return sval: str | ba.Lstr if ba.internal.get_v1_account_state() == 'signed_in': sval = ba.charstr(SpecialChar.TICKET) + str( - ba.internal.get_v1_account_ticket_count()) + ba.internal.get_v1_account_ticket_count() + ) else: sval = ba.Lstr(resource='getTicketsWindow.titleText') if self._get_tickets_button: @@ -354,12 +424,15 @@ class StoreBrowserWindow(ba.Window): self._scrollwidget = ba.scrollwidget( parent=self._root_widget, highlight=False, - position=((self._width - self._scroll_width) * 0.5, - self._height - self._scroll_height - 79 - 48), + position=( + (self._width - self._scroll_width) * 0.5, + self._height - self._scroll_height - 79 - 48, + ), size=(self._scroll_width, self._scroll_height), claims_left_right=True, claims_tab=True, - selection_loops_to_parent=True) + selection_loops_to_parent=True, + ) # NOTE: this stuff is modified by the _Store class. # Should maybe clean that up. @@ -377,16 +450,18 @@ class StoreBrowserWindow(ba.Window): h_align='center', v_align='center', text=ba.Lstr(resource=self._r + '.loadingText'), - maxwidth=self._scroll_width * 0.9) + maxwidth=self._scroll_width * 0.9, + ) class _Request: - def __init__(self, window: StoreBrowserWindow): self._window = weakref.ref(window) data = {'tab': tab_id.value} - ba.timer(0.1, - ba.WeakCall(self._on_response, data), - timetype=ba.TimeType.REAL) + ba.timer( + 0.1, + ba.WeakCall(self._on_response, data), + timetype=ba.TimeType.REAL, + ) def _on_response(self, data: dict[str, Any] | None) -> None: # FIXME: clean this up. @@ -401,22 +476,32 @@ class StoreBrowserWindow(ba.Window): self._request = _Request(self) # Actually start the purchase locally. - def _purchase_check_result(self, item: str, is_ticket_purchase: bool, - result: dict[str, Any] | None) -> None: + def _purchase_check_result( + self, item: str, is_ticket_purchase: bool, result: dict[str, Any] | None + ) -> None: if result is None: ba.playsound(ba.getsound('error')) ba.screenmessage( ba.Lstr(resource='internal.unavailableNoConnectionText'), - color=(1, 0, 0)) + color=(1, 0, 0), + ) else: if is_ticket_purchase: if result['allow']: price = ba.internal.get_v1_account_misc_read_val( - 'price.' + item, None) - if (price is None or not isinstance(price, int) - or price <= 0): - print('Error; got invalid local price of', price, - 'for item', item) + 'price.' + item, None + ) + if ( + price is None + or not isinstance(price, int) + or price <= 0 + ): + print( + 'Error; got invalid local price of', + price, + 'for item', + item, + ) ba.playsound(ba.getsound('error')) else: ba.playsound(ba.getsound('click01')) @@ -424,14 +509,20 @@ class StoreBrowserWindow(ba.Window): else: if result['reason'] == 'versionTooOld': ba.playsound(ba.getsound('error')) - ba.screenmessage(ba.Lstr( - resource='getTicketsWindow.versionTooOldText'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr( + resource='getTicketsWindow.versionTooOldText' + ), + color=(1, 0, 0), + ) else: ba.playsound(ba.getsound('error')) - ba.screenmessage(ba.Lstr( - resource='getTicketsWindow.unavailableText'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr( + resource='getTicketsWindow.unavailableText' + ), + color=(1, 0, 0), + ) # Real in-app purchase. else: if result['allow']: @@ -439,18 +530,24 @@ class StoreBrowserWindow(ba.Window): else: if result['reason'] == 'versionTooOld': ba.playsound(ba.getsound('error')) - ba.screenmessage(ba.Lstr( - resource='getTicketsWindow.versionTooOldText'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr( + resource='getTicketsWindow.versionTooOldText' + ), + color=(1, 0, 0), + ) else: ba.playsound(ba.getsound('error')) - ba.screenmessage(ba.Lstr( - resource='getTicketsWindow.unavailableText'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr( + resource='getTicketsWindow.unavailableText' + ), + color=(1, 0, 0), + ) - def _do_purchase_check(self, - item: str, - is_ticket_purchase: bool = False) -> None: + def _do_purchase_check( + self, item: str, is_ticket_purchase: bool = False + ) -> None: from ba.internal import master_server_get # Here we ping the server to ask if it's valid for us to @@ -465,16 +562,19 @@ class StoreBrowserWindow(ba.Window): 'subplatform': app.subplatform, 'version': app.version, 'buildNumber': app.build_number, - 'purchaseType': 'ticket' if is_ticket_purchase else 'real' + 'purchaseType': 'ticket' if is_ticket_purchase else 'real', }, - callback=ba.WeakCall(self._purchase_check_result, item, - is_ticket_purchase), + callback=ba.WeakCall( + self._purchase_check_result, item, is_ticket_purchase + ), ) def buy(self, item: str) -> None: """Attempt to purchase the provided item.""" - from ba.internal import (get_available_sale_time, - get_store_item_name_translated) + from ba.internal import ( + get_available_sale_time, + get_store_item_name_translated, + ) from bastd.ui import account from bastd.ui.confirm import ConfirmWindow from bastd.ui import getcurrency @@ -482,8 +582,10 @@ class StoreBrowserWindow(ba.Window): # Prevent pressing buy within a few seconds of the last press # (gives the buttons time to disable themselves and whatnot). curtime = ba.time(ba.TimeType.REAL) - if self._last_buy_time is not None and (curtime - - self._last_buy_time) < 2.0: + if ( + self._last_buy_time is not None + and (curtime - self._last_buy_time) < 2.0 + ): ba.playsound(ba.getsound('error')) else: if ba.internal.get_v1_account_state() != 'signed_in': @@ -497,11 +599,15 @@ class StoreBrowserWindow(ba.Window): # Purchase either pro or pro_sale depending on whether # there is a sale going on. - self._do_purchase_check('pro' if get_available_sale_time( - 'extras') is None else 'pro_sale') + self._do_purchase_check( + 'pro' + if get_available_sale_time('extras') is None + else 'pro_sale' + ) else: price = ba.internal.get_v1_account_misc_read_val( - 'price.' + item, None) + 'price.' + item, None + ) our_tickets = ba.internal.get_v1_account_ticket_count() if price is not None and our_tickets < price: ba.playsound(ba.getsound('error')) @@ -509,26 +615,38 @@ class StoreBrowserWindow(ba.Window): else: def do_it() -> None: - self._do_purchase_check(item, - is_ticket_purchase=True) + self._do_purchase_check( + item, is_ticket_purchase=True + ) ba.playsound(ba.getsound('swish')) ConfirmWindow( - ba.Lstr(resource='store.purchaseConfirmText', - subs=[ - ('${ITEM}', - get_store_item_name_translated(item)) - ]), + ba.Lstr( + resource='store.purchaseConfirmText', + subs=[ + ( + '${ITEM}', + get_store_item_name_translated(item), + ) + ], + ), width=400, height=120, action=do_it, - ok_text=ba.Lstr(resource='store.purchaseText', - fallback_resource='okText')) + ok_text=ba.Lstr( + resource='store.purchaseText', + fallback_resource='okText', + ), + ) def _print_already_own(self, charname: str) -> None: - ba.screenmessage(ba.Lstr(resource=self._r + '.alreadyOwnText', - subs=[('${NAME}', charname)]), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr( + resource=self._r + '.alreadyOwnText', + subs=[('${NAME}', charname)], + ), + color=(1, 0, 0), + ) ba.playsound(ba.getsound('error')) def update_buttons(self) -> None: @@ -538,20 +656,24 @@ class StoreBrowserWindow(ba.Window): # pylint: disable=too-many-locals from ba.internal import get_available_sale_time from ba import SpecialChar + if not self._root_widget: return import datetime + sales_raw = ba.internal.get_v1_account_misc_read_val('sales', {}) sales = {} try: # Look at the current set of sales; filter any with time remaining. for sale_item, sale_info in list(sales_raw.items()): - to_end = (datetime.datetime.utcfromtimestamp(sale_info['e']) - - datetime.datetime.utcnow()).total_seconds() + to_end = ( + datetime.datetime.utcfromtimestamp(sale_info['e']) + - datetime.datetime.utcnow() + ).total_seconds() if to_end > 0: sales[sale_item] = { 'to_end': to_end, - 'original_price': sale_info['op'] + 'original_price': sale_info['op'], } except Exception: ba.print_exception('Error parsing sales.') @@ -589,18 +711,21 @@ class StoreBrowserWindow(ba.Window): sale_time = get_available_sale_time('extras') if sale_time is not None: priceraw = ba.internal.get_price('pro') - price_text_left = (priceraw - if priceraw is not None else '?') + price_text_left = ( + priceraw if priceraw is not None else '?' + ) priceraw = ba.internal.get_price('pro_sale') - price_text_right = (priceraw - if priceraw is not None else '?') + price_text_right = ( + priceraw if priceraw is not None else '?' + ) sale_opacity = 1.0 price_text = '' sale_title_text = ba.Lstr(resource='store.saleText') sale_time_text = ba.timestring( sale_time, centi=False, - timeformat=ba.TimeFormat.MILLISECONDS) + timeformat=ba.TimeFormat.MILLISECONDS, + ) else: priceraw = ba.internal.get_price('pro') price_text = priceraw if priceraw is not None else '?' @@ -608,7 +733,8 @@ class StoreBrowserWindow(ba.Window): price_text_right = '' else: price = ba.internal.get_v1_account_misc_read_val( - 'price.' + b_type, 0) + 'price.' + b_type, 0 + ) # Color the button differently if we cant afford this. if ba.internal.get_v1_account_state() == 'signed_in': @@ -616,7 +742,9 @@ class StoreBrowserWindow(ba.Window): color = (0.6, 0.61, 0.6) price_text = ba.charstr(ba.SpecialChar.TICKET) + str( ba.internal.get_v1_account_misc_read_val( - 'price.' + b_type, '?')) + 'price.' + b_type, '?' + ) + ) price_text_left = '' price_text_right = '' @@ -624,14 +752,16 @@ class StoreBrowserWindow(ba.Window): if b_type in sales: sale_opacity = 1.0 price_text_left = ba.charstr(SpecialChar.TICKET) + str( - sales[b_type]['original_price']) + sales[b_type]['original_price'] + ) price_text_right = price_text price_text = '' sale_title_text = ba.Lstr(resource='store.saleText') sale_time_text = ba.timestring( int(sales[b_type]['to_end'] * 1000), centi=False, - timeformat=ba.TimeFormat.MILLISECONDS) + timeformat=ba.TimeFormat.MILLISECONDS, + ) description_color = (0.5, 1.0, 0.5) description_color2 = (0.3, 1.0, 1.0) @@ -641,39 +771,49 @@ class StoreBrowserWindow(ba.Window): if 'title_text' in b_info: ba.textwidget(edit=b_info['title_text'], color=title_color) if 'purchase_check' in b_info: - ba.imagewidget(edit=b_info['purchase_check'], - opacity=1.0 if show_purchase_check else 0.0) + ba.imagewidget( + edit=b_info['purchase_check'], + opacity=1.0 if show_purchase_check else 0.0, + ) if 'price_widget' in b_info: - ba.textwidget(edit=b_info['price_widget'], - text=price_text, - color=price_color) + ba.textwidget( + edit=b_info['price_widget'], + text=price_text, + color=price_color, + ) if 'price_widget_left' in b_info: - ba.textwidget(edit=b_info['price_widget_left'], - text=price_text_left) + ba.textwidget( + edit=b_info['price_widget_left'], text=price_text_left + ) if 'price_widget_right' in b_info: - ba.textwidget(edit=b_info['price_widget_right'], - text=price_text_right) + ba.textwidget( + edit=b_info['price_widget_right'], text=price_text_right + ) if 'price_slash_widget' in b_info: - ba.imagewidget(edit=b_info['price_slash_widget'], - opacity=sale_opacity) + ba.imagewidget( + edit=b_info['price_slash_widget'], opacity=sale_opacity + ) if 'sale_bg_widget' in b_info: - ba.imagewidget(edit=b_info['sale_bg_widget'], - opacity=sale_opacity) + ba.imagewidget( + edit=b_info['sale_bg_widget'], opacity=sale_opacity + ) if 'sale_title_widget' in b_info: - ba.textwidget(edit=b_info['sale_title_widget'], - text=sale_title_text) + ba.textwidget( + edit=b_info['sale_title_widget'], text=sale_title_text + ) if 'sale_time_widget' in b_info: - ba.textwidget(edit=b_info['sale_time_widget'], - text=sale_time_text) + ba.textwidget( + edit=b_info['sale_time_widget'], text=sale_time_text + ) if 'button' in b_info: - ba.buttonwidget(edit=b_info['button'], - color=color, - on_activate_call=call) + ba.buttonwidget( + edit=b_info['button'], color=color, on_activate_call=call + ) if 'extra_backings' in b_info: for bck in b_info['extra_backings']: - ba.imagewidget(edit=bck, - color=color, - opacity=extra_image_opacity) + ba.imagewidget( + edit=bck, color=color, opacity=extra_image_opacity + ) if 'extra_images' in b_info: for img in b_info['extra_images']: ba.imagewidget(edit=img, opacity=extra_image_opacity) @@ -684,8 +824,9 @@ class StoreBrowserWindow(ba.Window): for etxt in b_info['extra_texts_2']: ba.textwidget(edit=etxt, color=description_color2) if 'descriptionText' in b_info: - ba.textwidget(edit=b_info['descriptionText'], - color=description_color) + ba.textwidget( + edit=b_info['descriptionText'], color=description_color + ) def _on_response(self, data: dict[str, Any] | None) -> None: # pylint: disable=too-many-statements @@ -706,15 +847,22 @@ class StoreBrowserWindow(ba.Window): h_align='center', v_align='center', text=ba.Lstr(resource=self._r + '.loadErrorText'), - maxwidth=self._scroll_width * 0.9) + maxwidth=self._scroll_width * 0.9, + ) else: class _Store: + def __init__( + self, + store_window: StoreBrowserWindow, + sdata: dict[str, Any], + width: float, + ): + from ba.internal import ( + get_store_item_display_size, + get_store_layout, + ) - def __init__(self, store_window: StoreBrowserWindow, - sdata: dict[str, Any], width: float): - from ba.internal import (get_store_item_display_size, - get_store_layout) self._store_window = store_window self._width = width store_data = get_store_layout() @@ -737,25 +885,40 @@ class StoreBrowserWindow(ba.Window): else: dummy_name = '' section['button_size'] = get_store_item_display_size( - dummy_name) - section['v_spacing'] = (-17 if self._tab - == 'characters' else 0) + dummy_name + ) + section['v_spacing'] = ( + -17 if self._tab == 'characters' else 0 + ) if 'title' not in section: section['title'] = '' - section['x_offs'] = (130 if self._tab == 'extras' else - 270 if self._tab == 'maps' else 0) + section['x_offs'] = ( + 130 + if self._tab == 'extras' + else 270 + if self._tab == 'maps' + else 0 + ) section['y_offs'] = ( - 55 if (self._tab == 'extras' - and uiscale is ba.UIScale.SMALL) else - -20 if self._tab == 'icons' else 0) + 55 + if ( + self._tab == 'extras' + and uiscale is ba.UIScale.SMALL + ) + else -20 + if self._tab == 'icons' + else 0 + ) - def instantiate(self, scrollwidget: ba.Widget, - tab_button: ba.Widget) -> None: + def instantiate( + self, scrollwidget: ba.Widget, tab_button: ba.Widget + ) -> None: """Create the store.""" # pylint: disable=too-many-locals # pylint: disable=too-many-branches # pylint: disable=too-many-nested-blocks from bastd.ui.store import item as storeitemui + title_spacing = 40 button_border = 20 button_spacing = 4 @@ -769,93 +932,121 @@ class StoreBrowserWindow(ba.Window): self._height += title_spacing b_width, b_height = section['button_size'] b_column_count = int( - math.floor((self._width - boffs_h - 20) / - (b_width + button_spacing))) + math.floor( + (self._width - boffs_h - 20) + / (b_width + button_spacing) + ) + ) b_row_count = int( math.ceil( - float(len(section['items'])) / b_column_count)) + float(len(section['items'])) / b_column_count + ) + ) b_height_total = ( - 2 * button_border + b_row_count * b_height + - (b_row_count - 1) * section['v_spacing']) + 2 * button_border + + b_row_count * b_height + + (b_row_count - 1) * section['v_spacing'] + ) self._height += b_height_total assert self._height is not None - cnt2 = ba.containerwidget(parent=scrollwidget, - scale=1.0, - size=(self._width, self._height), - background=False, - claims_left_right=True, - claims_tab=True, - selection_loops_to_parent=True) + cnt2 = ba.containerwidget( + parent=scrollwidget, + scale=1.0, + size=(self._width, self._height), + background=False, + claims_left_right=True, + claims_tab=True, + selection_loops_to_parent=True, + ) v = self._height - 20 if self._tab == 'characters': txt = ba.Lstr( resource='store.howToSwitchCharactersText', subs=[ - ('${SETTINGS}', - ba.Lstr( - resource='accountSettingsWindow.titleText' - )), - ('${PLAYER_PROFILES}', - ba.Lstr( - resource='playerProfilesWindow.titleText') - ) - ]) - ba.textwidget(parent=cnt2, - text=txt, - size=(0, 0), - position=(self._width * 0.5, - self._height - 28), - h_align='center', - v_align='center', - color=(0.7, 1, 0.7, 0.4), - scale=0.7, - shadow=0, - flatness=1.0, - maxwidth=700, - transition_delay=0.4) + ( + '${SETTINGS}', + ba.Lstr( + resource=( + 'accountSettingsWindow.titleText' + ) + ), + ), + ( + '${PLAYER_PROFILES}', + ba.Lstr( + resource=( + 'playerProfilesWindow.titleText' + ) + ), + ), + ], + ) + ba.textwidget( + parent=cnt2, + text=txt, + size=(0, 0), + position=(self._width * 0.5, self._height - 28), + h_align='center', + v_align='center', + color=(0.7, 1, 0.7, 0.4), + scale=0.7, + shadow=0, + flatness=1.0, + maxwidth=700, + transition_delay=0.4, + ) elif self._tab == 'icons': txt = ba.Lstr( resource='store.howToUseIconsText', subs=[ - ('${SETTINGS}', - ba.Lstr(resource='mainMenu.settingsText')), - ('${PLAYER_PROFILES}', - ba.Lstr( - resource='playerProfilesWindow.titleText') - ) - ]) - ba.textwidget(parent=cnt2, - text=txt, - size=(0, 0), - position=(self._width * 0.5, - self._height - 28), - h_align='center', - v_align='center', - color=(0.7, 1, 0.7, 0.4), - scale=0.7, - shadow=0, - flatness=1.0, - maxwidth=700, - transition_delay=0.4) + ( + '${SETTINGS}', + ba.Lstr(resource='mainMenu.settingsText'), + ), + ( + '${PLAYER_PROFILES}', + ba.Lstr( + resource=( + 'playerProfilesWindow.titleText' + ) + ), + ), + ], + ) + ba.textwidget( + parent=cnt2, + text=txt, + size=(0, 0), + position=(self._width * 0.5, self._height - 28), + h_align='center', + v_align='center', + color=(0.7, 1, 0.7, 0.4), + scale=0.7, + shadow=0, + flatness=1.0, + maxwidth=700, + transition_delay=0.4, + ) elif self._tab == 'maps': assert self._width is not None assert self._height is not None txt = ba.Lstr(resource='store.howToUseMapsText') - ba.textwidget(parent=cnt2, - text=txt, - size=(0, 0), - position=(self._width * 0.5, - self._height - 28), - h_align='center', - v_align='center', - color=(0.7, 1, 0.7, 0.4), - scale=0.7, - shadow=0, - flatness=1.0, - maxwidth=700, - transition_delay=0.4) + ba.textwidget( + parent=cnt2, + text=txt, + size=(0, 0), + position=(self._width * 0.5, self._height - 28), + h_align='center', + v_align='center', + color=(0.7, 1, 0.7, 0.4), + scale=0.7, + shadow=0, + flatness=1.0, + maxwidth=700, + transition_delay=0.4, + ) prev_row_buttons: list | None = None this_row_buttons = [] @@ -873,23 +1064,29 @@ class StoreBrowserWindow(ba.Window): h_align='left', v_align='center', text=ba.Lstr(resource=section['title']), - maxwidth=self._width * 0.7) + maxwidth=self._width * 0.7, + ) v -= title_spacing delay = max(0.100, delay - 0.100) v -= button_border b_width, b_height = section['button_size'] b_count = len(section['items']) b_column_count = int( - math.floor((self._width - boffs_h - 20) / - (b_width + button_spacing))) + math.floor( + (self._width - boffs_h - 20) + / (b_width + button_spacing) + ) + ) col = 0 item: dict[str, Any] assert self._store_window.button_infos is not None for i, item_name in enumerate(section['items']): item = self._store_window.button_infos[ - item_name] = {} - item['call'] = ba.WeakCall(self._store_window.buy, - item_name) + item_name + ] = {} + item['call'] = ba.WeakCall( + self._store_window.buy, item_name + ) if 'x_offs' in section: boffs_h2 = section['x_offs'] else: @@ -899,9 +1096,12 @@ class StoreBrowserWindow(ba.Window): boffs_v2 = section['y_offs'] else: boffs_v2 = 0 - b_pos = (boffs_h + boffs_h2 + - (b_width + button_spacing) * col, - v - b_height + boffs_v2) + b_pos = ( + boffs_h + + boffs_h2 + + (b_width + button_spacing) * col, + v - b_height + boffs_v2, + ) storeitemui.instantiate_store_item_display( item_name, item, @@ -912,7 +1112,8 @@ class StoreBrowserWindow(ba.Window): b_height=b_height, boffs_h2=boffs_h2, boffs_v2=boffs_v2, - delay=delay) + delay=delay, + ) btn = item['button'] delay = max(0.1, delay - 0.1) this_row_buttons.append(btn) @@ -922,24 +1123,33 @@ class StoreBrowserWindow(ba.Window): if prev_row_buttons is not None: # pylint: disable=unsubscriptable-object if len(prev_row_buttons) > col: - ba.widget(edit=btn, - up_widget=prev_row_buttons[col]) - ba.widget(edit=prev_row_buttons[col], - down_widget=btn) + ba.widget( + edit=btn, + up_widget=prev_row_buttons[col], + ) + ba.widget( + edit=prev_row_buttons[col], + down_widget=btn, + ) # If we're the last button in our row, # wire any in the previous row past # our position to go to us if down is # pressed. - if (col + 1 == b_column_count - or i == b_count - 1): - for b_prev in prev_row_buttons[col + - 1:]: - ba.widget(edit=b_prev, - down_widget=btn) + if ( + col + 1 == b_column_count + or i == b_count - 1 + ): + for b_prev in prev_row_buttons[ + col + 1 : + ]: + ba.widget( + edit=b_prev, down_widget=btn + ) else: - ba.widget(edit=btn, - up_widget=prev_row_buttons[-1]) + ba.widget( + edit=btn, up_widget=prev_row_buttons[-1] + ) else: ba.widget(edit=btn, up_widget=tab_button) @@ -960,32 +1170,41 @@ class StoreBrowserWindow(ba.Window): 0.5, ba.WeakCall(self._store_window.update_buttons), repeat=True, - timetype=ba.TimeType.REAL) + timetype=ba.TimeType.REAL, + ) # Also update them immediately. self._store_window.update_buttons() - if self._current_tab in (self.TabID.EXTRAS, self.TabID.MINIGAMES, - self.TabID.CHARACTERS, self.TabID.MAPS, - self.TabID.ICONS): + if self._current_tab in ( + self.TabID.EXTRAS, + self.TabID.MINIGAMES, + self.TabID.CHARACTERS, + self.TabID.MAPS, + self.TabID.ICONS, + ): store = _Store(self, data, self._scroll_width) assert self._scrollwidget is not None store.instantiate( scrollwidget=self._scrollwidget, - tab_button=self._tab_row.tabs[self._current_tab].button) + tab_button=self._tab_row.tabs[self._current_tab].button, + ) else: - cnt = ba.containerwidget(parent=self._scrollwidget, - scale=1.0, - size=(self._scroll_width, - self._scroll_height * 0.95), - background=False, - claims_left_right=True, - claims_tab=True, - selection_loops_to_parent=True) + cnt = ba.containerwidget( + parent=self._scrollwidget, + scale=1.0, + size=(self._scroll_width, self._scroll_height * 0.95), + background=False, + claims_left_right=True, + claims_tab=True, + selection_loops_to_parent=True, + ) self._status_textwidget = ba.textwidget( parent=cnt, - position=(self._scroll_width * 0.5, - self._scroll_height * 0.5), + position=( + self._scroll_width * 0.5, + self._scroll_height * 0.5, + ), size=(0, 0), scale=1.3, transition_delay=0.1, @@ -993,13 +1212,15 @@ class StoreBrowserWindow(ba.Window): h_align='center', v_align='center', text=ba.Lstr(resource=self._r + '.comingSoonText'), - maxwidth=self._scroll_width * 0.9) + maxwidth=self._scroll_width * 0.9, + ) def _save_state(self) -> None: try: sel = self._root_widget.get_selected_child() selected_tab_ids = [ - tab_id for tab_id, tab in self._tab_row.tabs.items() + tab_id + for tab_id, tab in self._tab_row.tabs.items() if sel == tab.button ] if sel == self._get_tickets_button: @@ -1021,15 +1242,18 @@ class StoreBrowserWindow(ba.Window): def _restore_state(self) -> None: from efro.util import enum_by_value + try: sel: ba.Widget | None - sel_name = ba.app.ui.window_states.get(type(self), - {}).get('sel_name') + sel_name = ba.app.ui.window_states.get(type(self), {}).get( + 'sel_name' + ) assert isinstance(sel_name, (str, type(None))) try: - current_tab = enum_by_value(self.TabID, - ba.app.config.get('Store Tab')) + current_tab = enum_by_value( + self.TabID, ba.app.config.get('Store Tab') + ) except ValueError: current_tab = self.TabID.CHARACTERS @@ -1043,8 +1267,9 @@ class StoreBrowserWindow(ba.Window): sel = self._scrollwidget elif isinstance(sel_name, str) and sel_name.startswith('Tab:'): try: - sel_tab_id = enum_by_value(self.TabID, - sel_name.split(':')[-1]) + sel_tab_id = enum_by_value( + self.TabID, sel_name.split(':')[-1] + ) except ValueError: sel_tab_id = self.TabID.CHARACTERS sel = self._tab_row.tabs[sel_tab_id].button @@ -1052,8 +1277,10 @@ class StoreBrowserWindow(ba.Window): sel = self._tab_row.tabs[current_tab].button # If we were requested to show a tab, select it too.. - if (self._show_tab is not None - and self._show_tab in self._tab_row.tabs): + if ( + self._show_tab is not None + and self._show_tab in self._tab_row.tabs + ): sel = self._tab_row.tabs[self._show_tab].button self._set_tab(current_tab) if sel is not None: @@ -1065,6 +1292,7 @@ class StoreBrowserWindow(ba.Window): # pylint: disable=cyclic-import from bastd.ui.account import show_sign_in_prompt from bastd.ui.getcurrency import GetCurrencyWindow + if ba.internal.get_v1_account_state() != 'signed_in': show_sign_in_prompt() return @@ -1072,7 +1300,8 @@ class StoreBrowserWindow(ba.Window): ba.containerwidget(edit=self._root_widget, transition='out_left') window = GetCurrencyWindow( from_modal_store=self._modal, - store_back_location=self._back_location).get_root_widget() + store_back_location=self._back_location, + ).get_root_widget() if not self._modal: ba.app.ui.set_main_menu_window(window) @@ -1080,15 +1309,19 @@ class StoreBrowserWindow(ba.Window): # pylint: disable=cyclic-import from bastd.ui.coop.browser import CoopBrowserWindow from bastd.ui.mainmenu import MainMenuWindow + self._save_state() - ba.containerwidget(edit=self._root_widget, - transition=self._transition_out) + ba.containerwidget( + edit=self._root_widget, transition=self._transition_out + ) if not self._modal: if self._back_location == 'CoopBrowserWindow': ba.app.ui.set_main_menu_window( - CoopBrowserWindow(transition='in_left').get_root_widget()) + CoopBrowserWindow(transition='in_left').get_root_widget() + ) else: ba.app.ui.set_main_menu_window( - MainMenuWindow(transition='in_left').get_root_widget()) + MainMenuWindow(transition='in_left').get_root_widget() + ) if self._on_close_call is not None: self._on_close_call() diff --git a/dist/ba_data/python/bastd/ui/store/button.py b/dist/ba_data/python/bastd/ui/store/button.py index b9d4bf8..93ca4de 100644 --- a/dist/ba_data/python/bastd/ui/store/button.py +++ b/dist/ba_data/python/bastd/ui/store/button.py @@ -15,18 +15,20 @@ if TYPE_CHECKING: class StoreButton: """A button leading to the store.""" - def __init__(self, - parent: ba.Widget, - position: Sequence[float], - size: Sequence[float], - scale: float, - on_activate_call: Callable[[], Any] | None = None, - transition_delay: float | None = None, - color: Sequence[float] | None = None, - textcolor: Sequence[float] | None = None, - show_tickets: bool = False, - button_type: str | None = None, - sale_scale: float = 1.0): + def __init__( + self, + parent: ba.Widget, + position: Sequence[float], + size: Sequence[float], + scale: float, + on_activate_call: Callable[[], Any] | None = None, + transition_delay: float | None = None, + color: Sequence[float] | None = None, + textcolor: Sequence[float] | None = None, + show_tickets: bool = False, + button_type: str | None = None, + sale_scale: float = 1.0, + ): self._position = position self._size = size self._scale = scale @@ -44,7 +46,8 @@ class StoreButton: on_activate_call=self._on_activate, transition_delay=transition_delay, color=color, - button_type=button_type) + button_type=button_type, + ) self._title_text: ba.Widget | None self._ticket_text: ba.Widget | None @@ -52,8 +55,10 @@ class StoreButton: if show_tickets: self._title_text = ba.textwidget( parent=parent, - position=(position[0] + size[0] * 0.5 * scale, - position[1] + size[1] * 0.65 * scale), + position=( + position[0] + size[0] * 0.5 * scale, + position[1] + size[1] * 0.65 * scale, + ), size=(0, 0), h_align='center', v_align='center', @@ -62,7 +67,8 @@ class StoreButton: draw_controller=self._button, scale=scale, transition_delay=transition_delay, - color=textcolor) + color=textcolor, + ) self._ticket_text = ba.textwidget( parent=parent, size=(0, 0), @@ -74,7 +80,8 @@ class StoreButton: flatness=1.0, shadow=0.0, scale=scale * 0.6, - transition_delay=transition_delay) + transition_delay=transition_delay, + ) else: self._title_text = None self._ticket_text = None @@ -89,7 +96,8 @@ class StoreButton: draw_controller=self._button, size=(2.2 * self._circle_rad, 2.2 * self._circle_rad), texture=ba.gettexture('circleShadow'), - transition_delay=transition_delay) + transition_delay=transition_delay, + ) self._available_purchase_text = ba.textwidget( parent=parent, size=(0, 0), @@ -102,7 +110,8 @@ class StoreButton: shadow=1.0, scale=0.6 * scale, maxwidth=self._circle_rad * 1.4, - transition_delay=transition_delay) + transition_delay=transition_delay, + ) self._sale_circle_rad = 18 * scale * sale_scale self._sale_backing = ba.imagewidget( @@ -111,7 +120,8 @@ class StoreButton: draw_controller=self._button, size=(2 * self._sale_circle_rad, 2 * self._sale_circle_rad), texture=ba.gettexture('circleZigZag'), - transition_delay=transition_delay) + transition_delay=transition_delay, + ) self._sale_title_text = ba.textwidget( parent=parent, size=(0, 0), @@ -123,25 +133,29 @@ class StoreButton: shadow=0.0, scale=0.5 * scale * sale_scale, maxwidth=self._sale_circle_rad * 1.5, - transition_delay=transition_delay) - self._sale_time_text = ba.textwidget(parent=parent, - size=(0, 0), - h_align='center', - v_align='center', - draw_controller=self._button, - color=(0, 1, 0), - flatness=1.0, - shadow=0.0, - scale=0.4 * scale * sale_scale, - maxwidth=self._sale_circle_rad * - 1.5, - transition_delay=transition_delay) + transition_delay=transition_delay, + ) + self._sale_time_text = ba.textwidget( + parent=parent, + size=(0, 0), + h_align='center', + v_align='center', + draw_controller=self._button, + color=(0, 1, 0), + flatness=1.0, + shadow=0.0, + scale=0.4 * scale * sale_scale, + maxwidth=self._sale_circle_rad * 1.5, + transition_delay=transition_delay, + ) self.set_position(position) - self._update_timer = ba.Timer(1.0, - ba.WeakCall(self._update), - repeat=True, - timetype=ba.TimeType.REAL) + self._update_timer = ba.Timer( + 1.0, + ba.WeakCall(self._update), + repeat=True, + timetype=ba.TimeType.REAL, + ) self._update() def _on_activate(self) -> None: @@ -151,52 +165,73 @@ class StoreButton: def set_position(self, position: Sequence[float]) -> None: """Set the button position.""" self._position = position - self._circle_center = (position[0] + 0.1 * self._size[0] * self._scale, - position[1] + self._size[1] * self._scale * 0.8) - self._sale_circle_center = (position[0] + - 0.07 * self._size[0] * self._scale, - position[1] + - self._size[1] * self._scale * 0.8) + self._circle_center = ( + position[0] + 0.1 * self._size[0] * self._scale, + position[1] + self._size[1] * self._scale * 0.8, + ) + self._sale_circle_center = ( + position[0] + 0.07 * self._size[0] * self._scale, + position[1] + self._size[1] * self._scale * 0.8, + ) if not self._button: return ba.buttonwidget(edit=self._button, position=self._position) if self._title_text is not None: - ba.textwidget(edit=self._title_text, - position=(self._position[0] + - self._size[0] * 0.5 * self._scale, - self._position[1] + - self._size[1] * 0.65 * self._scale)) + ba.textwidget( + edit=self._title_text, + position=( + self._position[0] + self._size[0] * 0.5 * self._scale, + self._position[1] + self._size[1] * 0.65 * self._scale, + ), + ) if self._ticket_text is not None: ba.textwidget( edit=self._ticket_text, - position=(position[0] + self._size[0] * 0.5 * self._scale, - position[1] + self._size[1] * 0.28 * self._scale), - size=(0, 0)) + position=( + position[0] + self._size[0] * 0.5 * self._scale, + position[1] + self._size[1] * 0.28 * self._scale, + ), + size=(0, 0), + ) ba.imagewidget( edit=self._available_purchase_backing, - position=(self._circle_center[0] - self._circle_rad * 1.02, - self._circle_center[1] - self._circle_rad * 1.13)) - ba.textwidget(edit=self._available_purchase_text, - position=self._circle_center) + position=( + self._circle_center[0] - self._circle_rad * 1.02, + self._circle_center[1] - self._circle_rad * 1.13, + ), + ) + ba.textwidget( + edit=self._available_purchase_text, position=self._circle_center + ) ba.imagewidget( edit=self._sale_backing, - position=(self._sale_circle_center[0] - self._sale_circle_rad, - self._sale_circle_center[1] - self._sale_circle_rad)) - ba.textwidget(edit=self._sale_title_text, - position=(self._sale_circle_center[0], - self._sale_circle_center[1] + - self._sale_circle_rad * 0.3)) - ba.textwidget(edit=self._sale_time_text, - position=(self._sale_circle_center[0], - self._sale_circle_center[1] - - self._sale_circle_rad * 0.3)) + position=( + self._sale_circle_center[0] - self._sale_circle_rad, + self._sale_circle_center[1] - self._sale_circle_rad, + ), + ) + ba.textwidget( + edit=self._sale_title_text, + position=( + self._sale_circle_center[0], + self._sale_circle_center[1] + self._sale_circle_rad * 0.3, + ), + ) + ba.textwidget( + edit=self._sale_time_text, + position=( + self._sale_circle_center[0], + self._sale_circle_center[1] - self._sale_circle_rad * 0.3, + ), + ) def _default_on_activate_call(self) -> None: # pylint: disable=cyclic-import from bastd.ui.account import show_sign_in_prompt from bastd.ui.store.browser import StoreBrowserWindow + if ba.internal.get_v1_account_state() != 'signed_in': show_sign_in_prompt() return @@ -210,15 +245,19 @@ class StoreButton: # pylint: disable=too-many-branches # pylint: disable=cyclic-import from ba import SpecialChar, TimeFormat - from ba.internal import (get_available_sale_time, - get_available_purchase_count) + from ba.internal import ( + get_available_sale_time, + get_available_purchase_count, + ) + if not self._button: return # Our instance may outlive our UI objects. if self._ticket_text is not None: if ba.internal.get_v1_account_state() == 'signed_in': sval = ba.charstr(SpecialChar.TICKET) + str( - ba.internal.get_v1_account_ticket_count()) + ba.internal.get_v1_account_ticket_count() + ) else: sval = '-' ba.textwidget(edit=self._ticket_text, text=sval) @@ -230,6 +269,7 @@ class StoreButton: # ..also look for new style sales. if sale_time is None: import datetime + sales_raw = ba.internal.get_v1_account_misc_read_val('sales', {}) sale_times = [] try: @@ -237,9 +277,10 @@ class StoreButton: # remaining that we don't own. for sale_item, sale_info in list(sales_raw.items()): if not ba.internal.get_purchased(sale_item): - to_end = (datetime.datetime.utcfromtimestamp( - sale_info['e']) - - datetime.datetime.utcnow()).total_seconds() + to_end = ( + datetime.datetime.utcfromtimestamp(sale_info['e']) + - datetime.datetime.utcnow() + ).total_seconds() if to_end > 0: sale_times.append(to_end) except Exception: @@ -248,13 +289,16 @@ class StoreButton: sale_time = int(min(sale_times) * 1000) if sale_time is not None: - ba.textwidget(edit=self._sale_title_text, - text=ba.Lstr(resource='store.saleText')) - ba.textwidget(edit=self._sale_time_text, - text=ba.timestring( - sale_time, - centi=False, - timeformat=TimeFormat.MILLISECONDS)) + ba.textwidget( + edit=self._sale_title_text, + text=ba.Lstr(resource='store.saleText'), + ) + ba.textwidget( + edit=self._sale_time_text, + text=ba.timestring( + sale_time, centi=False, timeformat=TimeFormat.MILLISECONDS + ), + ) ba.imagewidget(edit=self._sale_backing, opacity=1.0) ba.imagewidget(edit=self._available_purchase_backing, opacity=1.0) ba.textwidget(edit=self._available_purchase_text, text='') @@ -264,11 +308,15 @@ class StoreButton: ba.textwidget(edit=self._sale_time_text, text='') ba.textwidget(edit=self._sale_title_text, text='') if available_purchases > 0: - ba.textwidget(edit=self._available_purchase_text, - text=str(available_purchases)) - ba.imagewidget(edit=self._available_purchase_backing, - opacity=1.0) + ba.textwidget( + edit=self._available_purchase_text, + text=str(available_purchases), + ) + ba.imagewidget( + edit=self._available_purchase_backing, opacity=1.0 + ) else: ba.textwidget(edit=self._available_purchase_text, text='') - ba.imagewidget(edit=self._available_purchase_backing, - opacity=0.0) + ba.imagewidget( + edit=self._available_purchase_backing, opacity=0.0 + ) diff --git a/dist/ba_data/python/bastd/ui/store/item.py b/dist/ba_data/python/bastd/ui/store/item.py index fb00ad4..246e84e 100644 --- a/dist/ba_data/python/bastd/ui/store/item.py +++ b/dist/ba_data/python/bastd/ui/store/item.py @@ -11,23 +11,29 @@ if TYPE_CHECKING: from typing import Any -def instantiate_store_item_display(item_name: str, - item: dict[str, Any], - parent_widget: ba.Widget, - b_pos: tuple[float, float], - b_width: float, - b_height: float, - boffs_h: float = 0.0, - boffs_h2: float = 0.0, - boffs_v2: float = 0, - delay: float = 0.0, - button: bool = True) -> None: +def instantiate_store_item_display( + item_name: str, + item: dict[str, Any], + parent_widget: ba.Widget, + b_pos: tuple[float, float], + b_width: float, + b_height: float, + boffs_h: float = 0.0, + boffs_h2: float = 0.0, + boffs_v2: float = 0, + delay: float = 0.0, + button: bool = True, +) -> None: """(internal)""" # pylint: disable=too-many-statements # pylint: disable=too-many-branches # pylint: disable=too-many-locals - from ba.internal import (get_store_item, get_store_item_name_translated, - get_clean_price) + from ba.internal import ( + get_store_item, + get_store_item_name_translated, + get_clean_price, + ) + del boffs_h # unused arg del boffs_h2 # unused arg del boffs_v2 # unused arg @@ -40,15 +46,17 @@ def instantiate_store_item_display(item_name: str, btn: ba.Widget | None if button: - item['button'] = btn = ba.buttonwidget(parent=parent_widget, - position=b_pos, - transition_delay=delay, - show_buffer_top=76.0, - enable_sound=False, - button_type='square', - size=(b_width, b_height), - autoselect=True, - label='') + item['button'] = btn = ba.buttonwidget( + parent=parent_widget, + position=b_pos, + transition_delay=delay, + show_buffer_top=76.0, + enable_sound=False, + button_type='square', + size=(b_width, b_height), + autoselect=True, + label='', + ) ba.widget(edit=btn, show_buffer_bottom=76.0) else: btn = None @@ -67,12 +75,19 @@ def instantiate_store_item_display(item_name: str, if item_name.startswith('characters.'): character = ba.app.spaz_appearances[item_info['character']] tint_color = ( - item_info['color'] if 'color' in item_info else - character.default_color if character.default_color is not None else - (1, 1, 1)) - tint2_color = (item_info['highlight'] if 'highlight' in item_info else - character.default_highlight if - character.default_highlight is not None else (1, 1, 1)) + item_info['color'] + if 'color' in item_info + else character.default_color + if character.default_color is not None + else (1, 1, 1) + ) + tint2_color = ( + item_info['highlight'] + if 'highlight' in item_info + else character.default_highlight + if character.default_highlight is not None + else (1, 1, 1) + ) icon_tex = character.icon_texture tint_tex = character.icon_mask_texture title_v = 0.255 @@ -98,12 +113,19 @@ def instantiate_store_item_display(item_name: str, modes_l.append(ba.Lstr(resource='playModes.freeForAllText')) if len(modes_l) == 3: - modes = ba.Lstr(value='${A}, ${B}, ${C}', - subs=[('${A}', modes_l[0]), ('${B}', modes_l[1]), - ('${C}', modes_l[2])]) + modes = ba.Lstr( + value='${A}, ${B}, ${C}', + subs=[ + ('${A}', modes_l[0]), + ('${B}', modes_l[1]), + ('${C}', modes_l[2]), + ], + ) elif len(modes_l) == 2: - modes = ba.Lstr(value='${A}, ${B}', - subs=[('${A}', modes_l[0]), ('${B}', modes_l[1])]) + modes = ba.Lstr( + value='${A}, ${B}', + subs=[('${A}', modes_l[0]), ('${B}', modes_l[1])], + ) elif len(modes_l) == 1: modes = modes_l[0] else: @@ -122,36 +144,44 @@ def instantiate_store_item_display(item_name: str, if item_name.startswith('characters.'): frame_size = b_width * 0.7 im_dim = frame_size * (100.0 / 113.0) - im_pos = (b_pos[0] + b_width * 0.5 - im_dim * 0.5 + b_offs_x, - b_pos[1] + b_height * 0.57 - im_dim * 0.5) + im_pos = ( + b_pos[0] + b_width * 0.5 - im_dim * 0.5 + b_offs_x, + b_pos[1] + b_height * 0.57 - im_dim * 0.5, + ) mask_texture = ba.gettexture('characterIconMask') assert icon_tex is not None assert tint_tex is not None - ba.imagewidget(parent=parent_widget, - position=im_pos, - size=(im_dim, im_dim), - color=(1, 1, 1), - transition_delay=delay, - mask_texture=mask_texture, - draw_controller=btn, - texture=ba.gettexture(icon_tex), - tint_texture=ba.gettexture(tint_tex), - tint_color=tint_color, - tint2_color=tint2_color) + ba.imagewidget( + parent=parent_widget, + position=im_pos, + size=(im_dim, im_dim), + color=(1, 1, 1), + transition_delay=delay, + mask_texture=mask_texture, + draw_controller=btn, + texture=ba.gettexture(icon_tex), + tint_texture=ba.gettexture(tint_tex), + tint_color=tint_color, + tint2_color=tint2_color, + ) if item_name in ['pro', 'upgrades.pro']: frame_size = b_width * 0.5 im_dim = frame_size * (100.0 / 113.0) - im_pos = (b_pos[0] + b_width * 0.5 - im_dim * 0.5 + b_offs_x, - b_pos[1] + b_height * 0.5 - im_dim * 0.5) - ba.imagewidget(parent=parent_widget, - position=im_pos, - size=(im_dim, im_dim), - transition_delay=delay, - draw_controller=btn, - color=(0.3, 0.0, 0.3), - opacity=0.3, - texture=ba.gettexture('logo')) + im_pos = ( + b_pos[0] + b_width * 0.5 - im_dim * 0.5 + b_offs_x, + b_pos[1] + b_height * 0.5 - im_dim * 0.5, + ) + ba.imagewidget( + parent=parent_widget, + position=im_pos, + size=(im_dim, im_dim), + transition_delay=delay, + draw_controller=btn, + color=(0.3, 0.0, 0.3), + opacity=0.3, + texture=ba.gettexture('logo'), + ) txt = ba.Lstr(resource='store.bombSquadProNewDescriptionText') # t = 'foo\nfoo\nfoo\nfoo\nfoo\nfoo' @@ -167,7 +197,8 @@ def instantiate_store_item_display(item_name: str, h_align='center', v_align='center', draw_controller=btn, - color=(0.3, 1, 0.3)) + color=(0.3, 1, 0.3), + ) extra_backings = item['extra_backings'] = [] extra_images = item['extra_images'] = [] @@ -182,174 +213,233 @@ def instantiate_store_item_display(item_name: str, tile_size = (b_width * 0.16 * 1.2, b_width * 0.2 * 1.2) tile_pos = (b_pos[0] + b_width * pos[0], b_pos[1] + b_height * pos[1]) extra_backings.append( - ba.imagewidget(parent=parent_widget, - position=(tile_pos[0] - tile_size[0] * 0.5, - tile_pos[1] - tile_size[1] * 0.5), - size=tile_size, - transition_delay=delay, - draw_controller=btn, - color=backing_color, - texture=b_square_texture)) + ba.imagewidget( + parent=parent_widget, + position=( + tile_pos[0] - tile_size[0] * 0.5, + tile_pos[1] - tile_size[1] * 0.5, + ), + size=tile_size, + transition_delay=delay, + draw_controller=btn, + color=backing_color, + texture=b_square_texture, + ) + ) im_size = tile_size[0] * 0.8 extra_images.append( - ba.imagewidget(parent=parent_widget, - position=(tile_pos[0] - im_size * 0.5, - tile_pos[1] - im_size * 0.4), - size=(im_size, im_size), - transition_delay=delay, - draw_controller=btn, - color=(1, 1, 1), - texture=ba.gettexture('ticketsMore'))) + ba.imagewidget( + parent=parent_widget, + position=( + tile_pos[0] - im_size * 0.5, + tile_pos[1] - im_size * 0.4, + ), + size=(im_size, im_size), + transition_delay=delay, + draw_controller=btn, + color=(1, 1, 1), + texture=ba.gettexture('ticketsMore'), + ) + ) bonus_tickets = str( - ba.internal.get_v1_account_misc_read_val('proBonusTickets', 100)) + ba.internal.get_v1_account_misc_read_val('proBonusTickets', 100) + ) extra_texts.append( - ba.textwidget(parent=parent_widget, - draw_controller=btn, - position=(tile_pos[0] - tile_size[0] * 0.03, - tile_pos[1] - tile_size[1] * 0.25), - size=(0, 0), - color=(0.6, 1, 0.6), - transition_delay=delay, - h_align='center', - v_align='center', - maxwidth=tile_size[0] * 0.7, - scale=0.55, - text=ba.Lstr(resource='getTicketsWindow.ticketsText', - subs=[('${COUNT}', bonus_tickets)]), - flatness=1.0, - shadow=0.0)) + ba.textwidget( + parent=parent_widget, + draw_controller=btn, + position=( + tile_pos[0] - tile_size[0] * 0.03, + tile_pos[1] - tile_size[1] * 0.25, + ), + size=(0, 0), + color=(0.6, 1, 0.6), + transition_delay=delay, + h_align='center', + v_align='center', + maxwidth=tile_size[0] * 0.7, + scale=0.55, + text=ba.Lstr( + resource='getTicketsWindow.ticketsText', + subs=[('${COUNT}', bonus_tickets)], + ), + flatness=1.0, + shadow=0.0, + ) + ) - for charname, pos in [('Kronk', (0.32, 0.45)), ('Zoe', (0.425, 0.4)), - ('Jack Morgan', (0.555, 0.45)), - ('Mel', (0.645, 0.4))]: + for charname, pos in [ + ('Kronk', (0.32, 0.45)), + ('Zoe', (0.425, 0.4)), + ('Jack Morgan', (0.555, 0.45)), + ('Mel', (0.645, 0.4)), + ]: tile_size = (b_width * 0.16 * 0.9, b_width * 0.2 * 0.9) - tile_pos = (b_pos[0] + b_width * pos[0], - b_pos[1] + b_height * pos[1]) + tile_pos = ( + b_pos[0] + b_width * pos[0], + b_pos[1] + b_height * pos[1], + ) character = ba.app.spaz_appearances[charname] extra_backings.append( - ba.imagewidget(parent=parent_widget, - position=(tile_pos[0] - tile_size[0] * 0.5, - tile_pos[1] - tile_size[1] * 0.5), - size=tile_size, - transition_delay=delay, - draw_controller=btn, - color=backing_color, - texture=b_square_texture)) + ba.imagewidget( + parent=parent_widget, + position=( + tile_pos[0] - tile_size[0] * 0.5, + tile_pos[1] - tile_size[1] * 0.5, + ), + size=tile_size, + transition_delay=delay, + draw_controller=btn, + color=backing_color, + texture=b_square_texture, + ) + ) im_size = tile_size[0] * 0.7 extra_images.append( - ba.imagewidget(parent=parent_widget, - position=(tile_pos[0] - im_size * 0.53, - tile_pos[1] - im_size * 0.35), - size=(im_size, im_size), - transition_delay=delay, - draw_controller=btn, - color=(1, 1, 1), - texture=ba.gettexture(character.icon_texture), - tint_texture=ba.gettexture( - character.icon_mask_texture), - tint_color=character.default_color, - tint2_color=character.default_highlight, - mask_texture=char_mask_texture)) + ba.imagewidget( + parent=parent_widget, + position=( + tile_pos[0] - im_size * 0.53, + tile_pos[1] - im_size * 0.35, + ), + size=(im_size, im_size), + transition_delay=delay, + draw_controller=btn, + color=(1, 1, 1), + texture=ba.gettexture(character.icon_texture), + tint_texture=ba.gettexture(character.icon_mask_texture), + tint_color=character.default_color, + tint2_color=character.default_highlight, + mask_texture=char_mask_texture, + ) + ) extra_texts.append( - ba.textwidget(parent=parent_widget, - draw_controller=btn, - position=(tile_pos[0] - im_size * 0.03, - tile_pos[1] - im_size * 0.51), - size=(0, 0), - color=(0.6, 1, 0.6), - transition_delay=delay, - h_align='center', - v_align='center', - maxwidth=tile_size[0] * 0.7, - scale=0.55, - text=ba.Lstr(translate=('characterNames', - charname)), - flatness=1.0, - shadow=0.0)) + ba.textwidget( + parent=parent_widget, + draw_controller=btn, + position=( + tile_pos[0] - im_size * 0.03, + tile_pos[1] - im_size * 0.51, + ), + size=(0, 0), + color=(0.6, 1, 0.6), + transition_delay=delay, + h_align='center', + v_align='center', + maxwidth=tile_size[0] * 0.7, + scale=0.55, + text=ba.Lstr(translate=('characterNames', charname)), + flatness=1.0, + shadow=0.0, + ) + ) # If we have a 'total-worth' item-id for this id, show that price so # the user knows how much this is worth. total_worth_item = ba.internal.get_v1_account_misc_read_val( - 'twrths', {}).get(item_name) + 'twrths', {} + ).get(item_name) total_worth_price: str | None if total_worth_item is not None: price = ba.internal.get_price(total_worth_item) - total_worth_price = (get_clean_price(price) - if price is not None else '??') + total_worth_price = ( + get_clean_price(price) if price is not None else '??' + ) else: total_worth_price = None if total_worth_price is not None: - total_worth_text = ba.Lstr(resource='store.totalWorthText', - subs=[('${TOTAL_WORTH}', - total_worth_price)]) + total_worth_text = ba.Lstr( + resource='store.totalWorthText', + subs=[('${TOTAL_WORTH}', total_worth_price)], + ) extra_texts_2.append( - ba.textwidget(parent=parent_widget, - text=total_worth_text, - position=(b_pos[0] + b_width * 0.5 + b_offs_x, - b_pos[1] + b_height * 0.25), - transition_delay=delay, - scale=b_width * (1.0 / 230.0) * base_text_scale * - 0.45, - maxwidth=b_width * 0.5, - size=(0, 0), - h_align='center', - v_align='center', - shadow=1.0, - flatness=1.0, - draw_controller=btn, - color=(0.3, 1, 1))) + ba.textwidget( + parent=parent_widget, + text=total_worth_text, + position=( + b_pos[0] + b_width * 0.5 + b_offs_x, + b_pos[1] + b_height * 0.25, + ), + transition_delay=delay, + scale=b_width * (1.0 / 230.0) * base_text_scale * 0.45, + maxwidth=b_width * 0.5, + size=(0, 0), + h_align='center', + v_align='center', + shadow=1.0, + flatness=1.0, + draw_controller=btn, + color=(0.3, 1, 1), + ) + ) model_opaque = ba.getmodel('level_select_button_opaque') model_transparent = ba.getmodel('level_select_button_transparent') mask_tex = ba.gettexture('mapPreviewMask') for levelname, preview_tex_name, pos in [ ('Infinite Onslaught', 'doomShroomPreview', (0.80, 0.48)), - ('Infinite Runaround', 'towerDPreview', (0.80, 0.32)) + ('Infinite Runaround', 'towerDPreview', (0.80, 0.32)), ]: tile_size = (b_width * 0.2, b_width * 0.13) - tile_pos = (b_pos[0] + b_width * pos[0], - b_pos[1] + b_height * pos[1]) + tile_pos = ( + b_pos[0] + b_width * pos[0], + b_pos[1] + b_height * pos[1], + ) im_size = tile_size[0] * 0.8 extra_backings.append( - ba.imagewidget(parent=parent_widget, - position=(tile_pos[0] - tile_size[0] * 0.5, - tile_pos[1] - tile_size[1] * 0.5), - size=tile_size, - transition_delay=delay, - draw_controller=btn, - color=backing_color, - texture=b_square_texture)) + ba.imagewidget( + parent=parent_widget, + position=( + tile_pos[0] - tile_size[0] * 0.5, + tile_pos[1] - tile_size[1] * 0.5, + ), + size=tile_size, + transition_delay=delay, + draw_controller=btn, + color=backing_color, + texture=b_square_texture, + ) + ) # Hack - gotta draw two transparent versions to avoid z issues. for mod in model_opaque, model_transparent: extra_images.append( - ba.imagewidget(parent=parent_widget, - position=(tile_pos[0] - im_size * 0.52, - tile_pos[1] - im_size * 0.2), - size=(im_size, im_size * 0.5), - transition_delay=delay, - model_transparent=mod, - mask_texture=mask_tex, - draw_controller=btn, - texture=ba.gettexture(preview_tex_name))) + ba.imagewidget( + parent=parent_widget, + position=( + tile_pos[0] - im_size * 0.52, + tile_pos[1] - im_size * 0.2, + ), + size=(im_size, im_size * 0.5), + transition_delay=delay, + model_transparent=mod, + mask_texture=mask_tex, + draw_controller=btn, + texture=ba.gettexture(preview_tex_name), + ) + ) extra_texts.append( - ba.textwidget(parent=parent_widget, - draw_controller=btn, - position=(tile_pos[0] - im_size * 0.03, - tile_pos[1] - im_size * 0.2), - size=(0, 0), - color=(0.6, 1, 0.6), - transition_delay=delay, - h_align='center', - v_align='center', - maxwidth=tile_size[0] * 0.7, - scale=0.55, - text=ba.Lstr(translate=('coopLevelNames', - levelname)), - flatness=1.0, - shadow=0.0)) + ba.textwidget( + parent=parent_widget, + draw_controller=btn, + position=( + tile_pos[0] - im_size * 0.03, + tile_pos[1] - im_size * 0.2, + ), + size=(0, 0), + color=(0.6, 1, 0.6), + transition_delay=delay, + h_align='center', + v_align='center', + maxwidth=tile_size[0] * 0.7, + scale=0.55, + text=ba.Lstr(translate=('coopLevelNames', levelname)), + flatness=1.0, + shadow=0.0, + ) + ) if item_name.startswith('icons.'): item['icon_text'] = ba.textwidget( @@ -363,45 +453,54 @@ def instantiate_store_item_display(item_name: str, size=(0, 0), h_align='center', v_align='center', - draw_controller=btn) + draw_controller=btn, + ) if item_name.startswith('maps.'): frame_size = b_width * 0.9 im_dim = frame_size * (100.0 / 113.0) - im_pos = (b_pos[0] + b_width * 0.5 - im_dim * 0.5 + b_offs_x, - b_pos[1] + b_height * 0.62 - im_dim * 0.25) + im_pos = ( + b_pos[0] + b_width * 0.5 - im_dim * 0.5 + b_offs_x, + b_pos[1] + b_height * 0.62 - im_dim * 0.25, + ) model_opaque = ba.getmodel('level_select_button_opaque') model_transparent = ba.getmodel('level_select_button_transparent') mask_tex = ba.gettexture('mapPreviewMask') assert tex_name is not None - ba.imagewidget(parent=parent_widget, - position=im_pos, - size=(im_dim, im_dim * 0.5), - transition_delay=delay, - model_opaque=model_opaque, - model_transparent=model_transparent, - mask_texture=mask_tex, - draw_controller=btn, - texture=ba.gettexture(tex_name)) + ba.imagewidget( + parent=parent_widget, + position=im_pos, + size=(im_dim, im_dim * 0.5), + transition_delay=delay, + model_opaque=model_opaque, + model_transparent=model_transparent, + mask_texture=mask_tex, + draw_controller=btn, + texture=ba.gettexture(tex_name), + ) if item_name.startswith('games.'): frame_size = b_width * 0.8 im_dim = frame_size * (100.0 / 113.0) - im_pos = (b_pos[0] + b_width * 0.5 - im_dim * 0.5 + b_offs_x, - b_pos[1] + b_height * 0.72 - im_dim * 0.25) + im_pos = ( + b_pos[0] + b_width * 0.5 - im_dim * 0.5 + b_offs_x, + b_pos[1] + b_height * 0.72 - im_dim * 0.25, + ) model_opaque = ba.getmodel('level_select_button_opaque') model_transparent = ba.getmodel('level_select_button_transparent') mask_tex = ba.gettexture('mapPreviewMask') assert tex_name is not None - ba.imagewidget(parent=parent_widget, - position=im_pos, - size=(im_dim, im_dim * 0.5), - transition_delay=delay, - model_opaque=model_opaque, - model_transparent=model_transparent, - mask_texture=mask_tex, - draw_controller=btn, - texture=ba.gettexture(tex_name)) + ba.imagewidget( + parent=parent_widget, + position=im_pos, + size=(im_dim, im_dim * 0.5), + transition_delay=delay, + model_opaque=model_opaque, + model_transparent=model_transparent, + mask_texture=mask_tex, + draw_controller=btn, + texture=ba.gettexture(tex_name), + ) item['descriptionText'] = ba.textwidget( parent=parent_widget, text=desc, @@ -416,7 +515,8 @@ def instantiate_store_item_display(item_name: str, draw_controller=btn, flatness=1.0, shadow=0.0, - color=(0.6, 1, 0.6)) + color=(0.6, 1, 0.6), + ) item['gameModesText'] = ba.textwidget( parent=parent_widget, text=modes, @@ -430,14 +530,17 @@ def instantiate_store_item_display(item_name: str, draw_controller=btn, shadow=0, flatness=1.0, - color=(0.6, 0.8, 0.6)) + color=(0.6, 0.8, 0.6), + ) if not item_name.startswith('icons.'): item['title_text'] = ba.textwidget( parent=parent_widget, text=title, - position=(b_pos[0] + b_width * 0.5 + b_offs_x, - b_pos[1] + b_height * title_v), + position=( + b_pos[0] + b_width * 0.5 + b_offs_x, + b_pos[1] + b_height * title_v, + ), transition_delay=delay, scale=b_width * (1.0 / 230.0) * base_text_scale, maxwidth=b_width * 0.8, @@ -445,7 +548,8 @@ def instantiate_store_item_display(item_name: str, h_align='center', v_align='center', draw_controller=btn, - color=(0.7, 0.9, 0.7, 1.0)) + color=(0.7, 0.9, 0.7, 1.0), + ) item['purchase_check'] = ba.imagewidget( parent=parent_widget, @@ -456,12 +560,15 @@ def instantiate_store_item_display(item_name: str, size=(60, 60), color=(0.6, 0.5, 0.8), draw_controller=btn, - texture=ba.gettexture('uiAtlas')) + texture=ba.gettexture('uiAtlas'), + ) item['price_widget'] = ba.textwidget( parent=parent_widget, text='', - position=(b_pos[0] + b_width * 0.5 + b_offs_x, - b_pos[1] + b_height * price_v), + position=( + b_pos[0] + b_width * 0.5 + b_offs_x, + b_pos[1] + b_height * price_v, + ), transition_delay=delay, scale=b_width * (1.0 / 300.0) * base_text_scale, maxwidth=b_width * 0.9, @@ -469,12 +576,15 @@ def instantiate_store_item_display(item_name: str, h_align='center', v_align='center', draw_controller=btn, - color=(0.2, 1, 0.2, 1.0)) + color=(0.2, 1, 0.2, 1.0), + ) item['price_widget_left'] = ba.textwidget( parent=parent_widget, text='', - position=(b_pos[0] + b_width * 0.33 + b_offs_x, - b_pos[1] + b_height * price_v), + position=( + b_pos[0] + b_width * 0.33 + b_offs_x, + b_pos[1] + b_height * price_v, + ), transition_delay=delay, scale=b_width * (1.0 / 300.0) * base_text_scale, maxwidth=b_width * 0.3, @@ -482,12 +592,15 @@ def instantiate_store_item_display(item_name: str, h_align='center', v_align='center', draw_controller=btn, - color=(0.2, 1, 0.2, 0.5)) + color=(0.2, 1, 0.2, 0.5), + ) item['price_widget_right'] = ba.textwidget( parent=parent_widget, text='', - position=(b_pos[0] + b_width * 0.66 + b_offs_x, - b_pos[1] + b_height * price_v), + position=( + b_pos[0] + b_width * 0.66 + b_offs_x, + b_pos[1] + b_height * price_v, + ), transition_delay=delay, scale=1.1 * b_width * (1.0 / 300.0) * base_text_scale, maxwidth=b_width * 0.3, @@ -495,20 +608,26 @@ def instantiate_store_item_display(item_name: str, h_align='center', v_align='center', draw_controller=btn, - color=(0.2, 1, 0.2, 1.0)) + color=(0.2, 1, 0.2, 1.0), + ) item['price_slash_widget'] = ba.imagewidget( parent=parent_widget, - position=(b_pos[0] + b_width * 0.33 + b_offs_x - 36, - b_pos[1] + b_height * price_v - 35), + position=( + b_pos[0] + b_width * 0.33 + b_offs_x - 36, + b_pos[1] + b_height * price_v - 35, + ), transition_delay=delay, texture=ba.gettexture('slash'), opacity=0.0, size=(70, 70), draw_controller=btn, - color=(1, 0, 0)) + color=(1, 0, 0), + ) badge_rad = 44 - badge_center = (b_pos[0] + b_width * 0.1 + b_offs_x, - b_pos[1] + b_height * 0.87) + badge_center = ( + b_pos[0] + b_width * 0.1 + b_offs_x, + b_pos[1] + b_height * 0.87, + ) item['sale_bg_widget'] = ba.imagewidget( parent=parent_widget, position=(badge_center[0] - badge_rad, badge_center[1] - badge_rad), @@ -517,30 +636,33 @@ def instantiate_store_item_display(item_name: str, texture=ba.gettexture('circleZigZag'), draw_controller=btn, size=(badge_rad * 2, badge_rad * 2), - color=(0.5, 0, 1)) - item['sale_title_widget'] = ba.textwidget(parent=parent_widget, - position=(badge_center[0], - badge_center[1] + 12), - transition_delay=delay, - scale=1.0, - maxwidth=badge_rad * 1.6, - size=(0, 0), - h_align='center', - v_align='center', - draw_controller=btn, - shadow=0.0, - flatness=1.0, - color=(0, 1, 0)) - item['sale_time_widget'] = ba.textwidget(parent=parent_widget, - position=(badge_center[0], - badge_center[1] - 12), - transition_delay=delay, - scale=0.7, - maxwidth=badge_rad * 1.6, - size=(0, 0), - h_align='center', - v_align='center', - draw_controller=btn, - shadow=0.0, - flatness=1.0, - color=(0.0, 1, 0.0, 1)) + color=(0.5, 0, 1), + ) + item['sale_title_widget'] = ba.textwidget( + parent=parent_widget, + position=(badge_center[0], badge_center[1] + 12), + transition_delay=delay, + scale=1.0, + maxwidth=badge_rad * 1.6, + size=(0, 0), + h_align='center', + v_align='center', + draw_controller=btn, + shadow=0.0, + flatness=1.0, + color=(0, 1, 0), + ) + item['sale_time_widget'] = ba.textwidget( + parent=parent_widget, + position=(badge_center[0], badge_center[1] - 12), + transition_delay=delay, + scale=0.7, + maxwidth=badge_rad * 1.6, + size=(0, 0), + h_align='center', + v_align='center', + draw_controller=btn, + shadow=0.0, + flatness=1.0, + color=(0.0, 1, 0.0, 1), + ) diff --git a/dist/ba_data/python/bastd/ui/tabs.py b/dist/ba_data/python/bastd/ui/tabs.py index fce571f..6ded23e 100644 --- a/dist/ba_data/python/bastd/ui/tabs.py +++ b/dist/ba_data/python/bastd/ui/tabs.py @@ -16,6 +16,7 @@ if TYPE_CHECKING: @dataclass class Tab: """Info for an individual tab in a TabRow""" + button: ba.Widget position: tuple[float, float] size: tuple[float, float] @@ -30,12 +31,14 @@ class TabRow(Generic[T]): Tabs are indexed by id which is an arbitrary user-provided type. """ - def __init__(self, - parent: ba.Widget, - tabdefs: list[tuple[T, ba.Lstr]], - pos: tuple[float, float], - size: tuple[float, float], - on_select_call: Callable[[T], None] | None = None) -> None: + def __init__( + self, + parent: ba.Widget, + tabdefs: list[tuple[T, ba.Lstr]], + pos: tuple[float, float], + size: tuple[float, float], + on_select_call: Callable[[T], None] | None = None, + ) -> None: if not tabdefs: raise ValueError('At least one tab def is required') self.tabs: dict[T, Tab] = {} @@ -46,16 +49,18 @@ class TabRow(Generic[T]): for tab_id, tab_label in tabdefs: pos = (h + tab_spacing * 0.5, tab_pos_v) size = (tab_button_width - tab_spacing, 50.0) - btn = ba.buttonwidget(parent=parent, - position=pos, - autoselect=True, - button_type='tab', - size=size, - label=tab_label, - enable_sound=False, - on_activate_call=ba.Call( - self._tick_and_call, on_select_call, - tab_id)) + btn = ba.buttonwidget( + parent=parent, + position=pos, + autoselect=True, + button_type='tab', + size=size, + label=tab_label, + enable_sound=False, + on_activate_call=ba.Call( + self._tick_and_call, on_select_call, tab_id + ), + ) h += tab_button_width self.tabs[tab_id] = Tab(button=btn, position=pos, size=size) @@ -63,16 +68,21 @@ class TabRow(Generic[T]): """Update appearances to make the provided tab appear selected.""" for tab_id, tab in self.tabs.items(): if tab_id == selected_tab_id: - ba.buttonwidget(edit=tab.button, - color=(0.5, 0.4, 0.93), - textcolor=(0.85, 0.75, 0.95)) # lit + ba.buttonwidget( + edit=tab.button, + color=(0.5, 0.4, 0.93), + textcolor=(0.85, 0.75, 0.95), + ) # lit else: - ba.buttonwidget(edit=tab.button, - color=(0.52, 0.48, 0.63), - textcolor=(0.65, 0.6, 0.7)) # unlit + ba.buttonwidget( + edit=tab.button, + color=(0.52, 0.48, 0.63), + textcolor=(0.65, 0.6, 0.7), + ) # unlit - def _tick_and_call(self, call: Callable[[Any], None] | None, - arg: Any) -> None: + def _tick_and_call( + self, call: Callable[[Any], None] | None, arg: Any + ) -> None: ba.playsound(ba.getsound('click01')) if call is not None: call(arg) diff --git a/dist/ba_data/python/bastd/ui/teamnamescolors.py b/dist/ba_data/python/bastd/ui/teamnamescolors.py index fc78971..959f444 100644 --- a/dist/ba_data/python/bastd/ui/teamnamescolors.py +++ b/dist/ba_data/python/bastd/ui/teamnamescolors.py @@ -19,6 +19,7 @@ class TeamNamesColorsWindow(popup.PopupWindow): def __init__(self, scale_origin: tuple[float, float]): from ba.internal import DEFAULT_TEAM_COLORS, DEFAULT_TEAM_NAMES + self._width = 500 self._height = 330 self._transitioning_out = False @@ -26,15 +27,21 @@ class TeamNamesColorsWindow(popup.PopupWindow): # Creates our _root_widget. uiscale = ba.app.ui.uiscale - scale = (1.69 if uiscale is ba.UIScale.SMALL else - 1.1 if uiscale is ba.UIScale.MEDIUM else 0.85) - super().__init__(position=scale_origin, - size=(self._width, self._height), - scale=scale) + scale = ( + 1.69 + if uiscale is ba.UIScale.SMALL + else 1.1 + if uiscale is ba.UIScale.MEDIUM + else 0.85 + ) + super().__init__( + position=scale_origin, size=(self._width, self._height), scale=scale + ) appconfig = ba.app.config self._names = list( - appconfig.get('Custom Team Names', DEFAULT_TEAM_NAMES)) + appconfig.get('Custom Team Names', DEFAULT_TEAM_NAMES) + ) # We need to flatten the translation since it will be an # editable string. @@ -42,7 +49,8 @@ class TeamNamesColorsWindow(popup.PopupWindow): ba.Lstr(translate=('teamNames', n)).evaluate() for n in self._names ] self._colors = list( - appconfig.get('Custom Team Colors', DEFAULT_TEAM_COLORS)) + appconfig.get('Custom Team Colors', DEFAULT_TEAM_COLORS) + ) self._color_buttons: list[ba.Widget] = [] self._color_text_fields: list[ba.Widget] = [] @@ -54,77 +62,99 @@ class TeamNamesColorsWindow(popup.PopupWindow): scale=0.7, on_activate_call=self._reset, size=(120, 50), - position=(self._width * 0.5 - 60 * 0.7, self._height - 60)) + position=(self._width * 0.5 - 60 * 0.7, self._height - 60), + ) for i in range(2): self._color_buttons.append( - ba.buttonwidget(parent=self.root_widget, - autoselect=True, - position=(50, 0 + 195 - 90 * i), - on_activate_call=ba.Call(self._color_click, i), - size=(70, 70), - color=self._colors[i], - label='', - button_type='square')) + ba.buttonwidget( + parent=self.root_widget, + autoselect=True, + position=(50, 0 + 195 - 90 * i), + on_activate_call=ba.Call(self._color_click, i), + size=(70, 70), + color=self._colors[i], + label='', + button_type='square', + ) + ) self._color_text_fields.append( - ba.textwidget(parent=self.root_widget, - position=(135, 0 + 201 - 90 * i), - size=(280, 46), - text=self._names[i], - h_align='left', - v_align='center', - max_chars=self._max_name_length, - color=self._colors[i], - description=ba.Lstr(resource='nameText'), - editable=True, - padding=4)) - ba.widget(edit=self._color_text_fields[0], - down_widget=self._color_text_fields[1]) - ba.widget(edit=self._color_text_fields[1], - up_widget=self._color_text_fields[0]) + ba.textwidget( + parent=self.root_widget, + position=(135, 0 + 201 - 90 * i), + size=(280, 46), + text=self._names[i], + h_align='left', + v_align='center', + max_chars=self._max_name_length, + color=self._colors[i], + description=ba.Lstr(resource='nameText'), + editable=True, + padding=4, + ) + ) + ba.widget( + edit=self._color_text_fields[0], + down_widget=self._color_text_fields[1], + ) + ba.widget( + edit=self._color_text_fields[1], + up_widget=self._color_text_fields[0], + ) ba.widget(edit=self._color_text_fields[0], up_widget=resetbtn) - cancelbtn = ba.buttonwidget(parent=self.root_widget, - label=ba.Lstr(resource='cancelText'), - autoselect=True, - on_activate_call=self._on_cancel_press, - size=(150, 50), - position=(self._width * 0.5 - 200, 20)) - okbtn = ba.buttonwidget(parent=self.root_widget, - label=ba.Lstr(resource='okText'), - autoselect=True, - on_activate_call=self._ok, - size=(150, 50), - position=(self._width * 0.5 + 50, 20)) - ba.containerwidget(edit=self.root_widget, - selected_child=self._color_buttons[0]) + cancelbtn = ba.buttonwidget( + parent=self.root_widget, + label=ba.Lstr(resource='cancelText'), + autoselect=True, + on_activate_call=self._on_cancel_press, + size=(150, 50), + position=(self._width * 0.5 - 200, 20), + ) + okbtn = ba.buttonwidget( + parent=self.root_widget, + label=ba.Lstr(resource='okText'), + autoselect=True, + on_activate_call=self._ok, + size=(150, 50), + position=(self._width * 0.5 + 50, 20), + ) + ba.containerwidget( + edit=self.root_widget, selected_child=self._color_buttons[0] + ) ba.widget(edit=okbtn, left_widget=cancelbtn) self._update() def _color_click(self, i: int) -> None: from bastd.ui.colorpicker import ColorPicker - ColorPicker(parent=self.root_widget, - position=self._color_buttons[i].get_screen_space_center(), - offset=(270.0, 0), - initial_color=self._colors[i], - delegate=self, - tag=i) + + ColorPicker( + parent=self.root_widget, + position=self._color_buttons[i].get_screen_space_center(), + offset=(270.0, 0), + initial_color=self._colors[i], + delegate=self, + tag=i, + ) def color_picker_closing(self, picker: ColorPicker) -> None: """Called when the color picker is closing.""" - def color_picker_selected_color(self, picker: ColorPicker, - color: Sequence[float]) -> None: + def color_picker_selected_color( + self, picker: ColorPicker, color: Sequence[float] + ) -> None: """Called when a color is selected in the color picker.""" self._colors[picker.get_tag()] = color self._update() def _reset(self) -> None: from ba.internal import DEFAULT_TEAM_NAMES, DEFAULT_TEAM_COLORS + for i in range(2): self._colors[i] = DEFAULT_TEAM_COLORS[i] - name = ba.Lstr(translate=('teamNames', - DEFAULT_TEAM_NAMES[i])).evaluate() + name = ba.Lstr( + translate=('teamNames', DEFAULT_TEAM_NAMES[i]) + ).evaluate() if len(name) > self._max_name_length: print('GOT DEFAULT TEAM NAME LONGER THAN MAX LENGTH') ba.textwidget(edit=self._color_text_fields[i], text=name) @@ -133,11 +163,13 @@ class TeamNamesColorsWindow(popup.PopupWindow): def _update(self) -> None: for i in range(2): ba.buttonwidget(edit=self._color_buttons[i], color=self._colors[i]) - ba.textwidget(edit=self._color_text_fields[i], - color=self._colors[i]) + ba.textwidget( + edit=self._color_text_fields[i], color=self._colors[i] + ) def _ok(self) -> None: from ba.internal import DEFAULT_TEAM_COLORS, DEFAULT_TEAM_NAMES + cfg = ba.app.config # First, determine whether the values here are defaults, in which case @@ -150,8 +182,9 @@ class TeamNamesColorsWindow(popup.PopupWindow): for i in range(2): name = cast(str, ba.textwidget(query=self._color_text_fields[i])) if not name: - ba.screenmessage(ba.Lstr(resource='nameNotEmptyText'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr(resource='nameNotEmptyText'), color=(1, 0, 0) + ) ba.playsound(ba.getsound('error')) return new_names.append(name) @@ -161,9 +194,12 @@ class TeamNamesColorsWindow(popup.PopupWindow): is_default = False default_team_name = DEFAULT_TEAM_NAMES[i] default_team_name_translated = ba.Lstr( - translate=('teamNames', default_team_name)).evaluate() - if ((new_names[i] != default_team_name - and new_names[i] != default_team_name_translated)): + translate=('teamNames', default_team_name) + ).evaluate() + if ( + new_names[i] != default_team_name + and new_names[i] != default_team_name_translated + ): is_default = False if is_default: diff --git a/dist/ba_data/python/bastd/ui/telnet.py b/dist/ba_data/python/bastd/ui/telnet.py index 0c7080c..80c0127 100644 --- a/dist/ba_data/python/bastd/ui/telnet.py +++ b/dist/ba_data/python/bastd/ui/telnet.py @@ -17,31 +17,45 @@ class TelnetAccessRequestWindow(ba.Window): text = ba.Lstr(resource='telnetAccessText') uiscale = ba.app.ui.uiscale - super().__init__(root_widget=ba.containerwidget( - size=(width, height + 40), - transition='in_right', - scale=(1.7 if uiscale is ba.UIScale.SMALL else - 1.3 if uiscale is ba.UIScale.MEDIUM else 1.0))) + super().__init__( + root_widget=ba.containerwidget( + size=(width, height + 40), + transition='in_right', + scale=( + 1.7 + if uiscale is ba.UIScale.SMALL + else 1.3 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), + ) + ) padding = 20 - ba.textwidget(parent=self._root_widget, - position=(padding, padding + 33), - size=(width - 2 * padding, height - 2 * padding), - h_align='center', - v_align='top', - text=text) - btn = ba.buttonwidget(parent=self._root_widget, - position=(20, 20), - size=(140, 50), - label=ba.Lstr(resource='denyText'), - on_activate_call=self._cancel) + ba.textwidget( + parent=self._root_widget, + position=(padding, padding + 33), + size=(width - 2 * padding, height - 2 * padding), + h_align='center', + v_align='top', + text=text, + ) + btn = ba.buttonwidget( + parent=self._root_widget, + position=(20, 20), + size=(140, 50), + label=ba.Lstr(resource='denyText'), + on_activate_call=self._cancel, + ) ba.containerwidget(edit=self._root_widget, cancel_button=btn) ba.containerwidget(edit=self._root_widget, selected_child=btn) - ba.buttonwidget(parent=self._root_widget, - position=(width - 155, 20), - size=(140, 50), - label=ba.Lstr(resource='allowText'), - on_activate_call=self._ok) + ba.buttonwidget( + parent=self._root_widget, + position=(width - 155, 20), + size=(140, 50), + label=ba.Lstr(resource='allowText'), + on_activate_call=self._ok, + ) def _cancel(self) -> None: ba.containerwidget(edit=self._root_widget, transition='out_right') diff --git a/dist/ba_data/python/bastd/ui/tournamententry.py b/dist/ba_data/python/bastd/ui/tournamententry.py index f543755..cf46191 100644 --- a/dist/ba_data/python/bastd/ui/tournamententry.py +++ b/dist/ba_data/python/bastd/ui/tournamententry.py @@ -17,14 +17,16 @@ if TYPE_CHECKING: class TournamentEntryWindow(popup.PopupWindow): """Popup window for entering tournaments.""" - def __init__(self, - tournament_id: str, - tournament_activity: ba.Activity | None = None, - position: tuple[float, float] = (0.0, 0.0), - delegate: Any = None, - scale: float | None = None, - offset: tuple[float, float] = (0.0, 0.0), - on_close_call: Callable[[], Any] | None = None): + def __init__( + self, + tournament_id: str, + tournament_activity: ba.Activity | None = None, + position: tuple[float, float] = (0.0, 0.0), + delegate: Any = None, + scale: float | None = None, + offset: tuple[float, float] = (0.0, 0.0), + on_close_call: Callable[[], Any] | None = None, + ): # Needs some tidying. # pylint: disable=too-many-branches # pylint: disable=too-many-statements @@ -32,8 +34,9 @@ class TournamentEntryWindow(popup.PopupWindow): ba.set_analytics_screen('Tournament Entry Window') self._tournament_id = tournament_id - self._tournament_info = ( - ba.app.accounts_v1.tournament_info[self._tournament_id]) + self._tournament_info = ba.app.accounts_v1.tournament_info[ + self._tournament_id + ] # Set a few vars depending on the tourney fee. self._fee = self._tournament_info['fee'] @@ -61,8 +64,13 @@ class TournamentEntryWindow(popup.PopupWindow): self._on_close_call = on_close_call if scale is None: uiscale = ba.app.ui.uiscale - scale = (2.3 if uiscale is ba.UIScale.SMALL else - 1.65 if uiscale is ba.UIScale.MEDIUM else 1.23) + scale = ( + 2.3 + if uiscale is ba.UIScale.SMALL + else 1.65 + if uiscale is ba.UIScale.MEDIUM + else 1.23 + ) self._delegate = delegate self._transitioning_out = False @@ -74,13 +82,15 @@ class TournamentEntryWindow(popup.PopupWindow): bg_color = (0.5, 0.4, 0.6) # Creates our root_widget. - popup.PopupWindow.__init__(self, - position=position, - size=(self._width, self._height), - scale=scale, - bg_color=bg_color, - offset=offset, - toolbar_visibility='menu_currency') + popup.PopupWindow.__init__( + self, + position=position, + size=(self._width, self._height), + scale=scale, + bg_color=bg_color, + offset=offset, + toolbar_visibility='menu_currency', + ) self._last_ad_press_time = -9999.0 self._last_ticket_press_time = -9999.0 @@ -88,20 +98,22 @@ class TournamentEntryWindow(popup.PopupWindow): self._launched = False # Show the ad button only if we support ads *and* it has a level 1 fee. - self._do_ad_btn = (ba.internal.has_video_ads() and self._allow_ads) + self._do_ad_btn = ba.internal.has_video_ads() and self._allow_ads x_offs = 0 if self._do_ad_btn else 85 - self._cancel_button = ba.buttonwidget(parent=self.root_widget, - position=(20, self._height - 34), - size=(60, 60), - scale=0.5, - label='', - color=bg_color, - on_activate_call=self._on_cancel, - autoselect=True, - icon=ba.gettexture('crossOut'), - iconscale=1.2) + self._cancel_button = ba.buttonwidget( + parent=self.root_widget, + position=(20, self._height - 34), + size=(60, 60), + scale=0.5, + label='', + color=bg_color, + on_activate_call=self._on_cancel, + autoselect=True, + icon=ba.gettexture('crossOut'), + iconscale=1.2, + ) self._title_text = ba.textwidget( parent=self.root_widget, @@ -112,7 +124,8 @@ class TournamentEntryWindow(popup.PopupWindow): scale=0.6, text=ba.Lstr(resource='tournamentEntryText'), maxwidth=180, - color=(1, 1, 1, 0.4)) + color=(1, 1, 1, 0.4), + ) btn = self._pay_with_tickets_button = ba.buttonwidget( parent=self.root_widget, @@ -121,14 +134,17 @@ class TournamentEntryWindow(popup.PopupWindow): button_type='square', size=(120, 120), label='', - on_activate_call=self._on_pay_with_tickets_press) + on_activate_call=self._on_pay_with_tickets_press, + ) self._ticket_img_pos = (50 + x_offs, 94) self._ticket_img_pos_free = (50 + x_offs, 80) - self._ticket_img = ba.imagewidget(parent=self.root_widget, - draw_controller=btn, - size=(80, 80), - position=self._ticket_img_pos, - texture=ba.gettexture('tickets')) + self._ticket_img = ba.imagewidget( + parent=self.root_widget, + draw_controller=btn, + size=(80, 80), + position=self._ticket_img_pos, + texture=ba.gettexture('tickets'), + ) self._ticket_cost_text_position = (87 + x_offs, 88) self._ticket_cost_text_position_free = (87 + x_offs, 120) self._ticket_cost_text = ba.textwidget( @@ -141,7 +157,8 @@ class TournamentEntryWindow(popup.PopupWindow): scale=0.6, text='', maxwidth=95, - color=(0, 1, 0)) + color=(0, 1, 0), + ) self._free_plays_remaining_text = ba.textwidget( parent=self.root_widget, draw_controller=btn, @@ -152,7 +169,8 @@ class TournamentEntryWindow(popup.PopupWindow): scale=0.33, text='', maxwidth=95, - color=(0, 0.8, 0)) + color=(0, 0.8, 0), + ) self._pay_with_ad_btn: ba.Widget | None if self._do_ad_btn: btn = self._pay_with_ad_btn = ba.buttonwidget( @@ -162,22 +180,27 @@ class TournamentEntryWindow(popup.PopupWindow): button_type='square', size=(120, 120), label='', - on_activate_call=self._on_pay_with_ad_press) - self._pay_with_ad_img = ba.imagewidget(parent=self.root_widget, - draw_controller=btn, - size=(80, 80), - position=(210, 94), - texture=ba.gettexture('tv')) + on_activate_call=self._on_pay_with_ad_press, + ) + self._pay_with_ad_img = ba.imagewidget( + parent=self.root_widget, + draw_controller=btn, + size=(80, 80), + position=(210, 94), + texture=ba.gettexture('tv'), + ) self._ad_text_position = (251, 88) self._ad_text_position_remaining = (251, 92) have_ad_tries_remaining = ( - self._tournament_info['adTriesRemaining'] is not None) + self._tournament_info['adTriesRemaining'] is not None + ) self._ad_text = ba.textwidget( parent=self.root_widget, draw_controller=btn, position=self._ad_text_position_remaining - if have_ad_tries_remaining else self._ad_text_position, + if have_ad_tries_remaining + else self._ad_text_position, size=(0, 0), h_align='center', v_align='center', @@ -186,10 +209,13 @@ class TournamentEntryWindow(popup.PopupWindow): # specifically says 'Ad' in it. text=ba.Lstr(resource='watchAnAdText'), maxwidth=95, - color=(0, 1, 0)) + color=(0, 1, 0), + ) ad_plays_remaining_text = ( - '' if not have_ad_tries_remaining else '' + - str(self._tournament_info['adTriesRemaining'])) + '' + if not have_ad_tries_remaining + else '' + str(self._tournament_info['adTriesRemaining']) + ) self._ad_plays_remaining_text = ba.textwidget( parent=self.root_widget, draw_controller=btn, @@ -200,18 +226,22 @@ class TournamentEntryWindow(popup.PopupWindow): scale=0.33, text=ad_plays_remaining_text, maxwidth=95, - color=(0, 0.8, 0)) + color=(0, 0.8, 0), + ) - ba.textwidget(parent=self.root_widget, - position=(self._width * 0.5, 120), - size=(0, 0), - h_align='center', - v_align='center', - scale=0.6, - text=ba.Lstr(resource='orText', - subs=[('${A}', ''), ('${B}', '')]), - maxwidth=35, - color=(1, 1, 1, 0.5)) + ba.textwidget( + parent=self.root_widget, + position=(self._width * 0.5, 120), + size=(0, 0), + h_align='center', + v_align='center', + scale=0.6, + text=ba.Lstr( + resource='orText', subs=[('${A}', ''), ('${B}', '')] + ), + maxwidth=35, + color=(1, 1, 1, 0.5), + ) else: self._pay_with_ad_btn = None @@ -228,7 +258,8 @@ class TournamentEntryWindow(popup.PopupWindow): textcolor=(0.2, 1, 0.2), label=ba.charstr(ba.SpecialChar.TICKET), color=(0.65, 0.5, 0.8), - on_activate_call=self._on_get_tickets_press) + on_activate_call=self._on_get_tickets_press, + ) else: self._ticket_count_text = ba.textwidget( parent=self.root_widget, @@ -236,12 +267,14 @@ class TournamentEntryWindow(popup.PopupWindow): position=(self._width - 190 + 125, self._height - 34), color=(0.2, 1, 0.2), h_align='center', - v_align='center') + v_align='center', + ) self._seconds_remaining = None - ba.containerwidget(edit=self.root_widget, - cancel_button=self._cancel_button) + ba.containerwidget( + edit=self.root_widget, cancel_button=self._cancel_button + ) # Let's also ask the server for info about this tournament # (time remaining, etc) so we can show the user time remaining, @@ -269,24 +302,39 @@ class TournamentEntryWindow(popup.PopupWindow): scale=0.45, flatness=1.0, maxwidth=100, - color=(0.7, 0.7, 0.7)) + color=(0.7, 0.7, 0.7), + ) self._last_query_time: float | None = None # If there seems to be a relatively-recent valid cached info for this # tournament, use it. Otherwise we'll kick off a query ourselves. - if (self._tournament_id in ba.app.accounts_v1.tournament_info - and ba.app.accounts_v1.tournament_info[ - self._tournament_id]['valid'] - and (ba.time(ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS) - - ba.app.accounts_v1.tournament_info[self._tournament_id] - ['timeReceived'] < 1000 * 60 * 5)): + if ( + self._tournament_id in ba.app.accounts_v1.tournament_info + and ba.app.accounts_v1.tournament_info[self._tournament_id]['valid'] + and ( + ba.time(ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS) + - ba.app.accounts_v1.tournament_info[self._tournament_id][ + 'timeReceived' + ] + < 1000 * 60 * 5 + ) + ): try: info = ba.app.accounts_v1.tournament_info[self._tournament_id] self._seconds_remaining = max( - 0, info['timeRemaining'] - int( - (ba.time(ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS) - - info['timeReceived']) / 1000)) + 0, + info['timeRemaining'] + - int( + ( + ba.time( + ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS + ) + - info['timeReceived'] + ) + / 1000 + ), + ) self._have_valid_data = True self._last_query_time = ba.time(ba.TimeType.REAL) except Exception: @@ -297,22 +345,26 @@ class TournamentEntryWindow(popup.PopupWindow): self._fg_state = ba.app.fg_state self._running_query = False - self._update_timer = ba.Timer(1.0, - ba.WeakCall(self._update), - repeat=True, - timetype=ba.TimeType.REAL) + self._update_timer = ba.Timer( + 1.0, + ba.WeakCall(self._update), + repeat=True, + timetype=ba.TimeType.REAL, + ) self._update() self._restore_state() - def _on_tournament_query_response(self, - data: dict[str, Any] | None) -> None: + def _on_tournament_query_response( + self, data: dict[str, Any] | None + ) -> None: accounts = ba.app.accounts_v1 self._running_query = False if data is not None: data = data['t'] # This used to be the whole payload. accounts.cache_tournament_info(data) self._seconds_remaining = accounts.tournament_info[ - self._tournament_id]['timeRemaining'] + self._tournament_id + ]['timeRemaining'] self._have_valid_data = True def _save_state(self) -> None: @@ -347,21 +399,25 @@ class TournamentEntryWindow(popup.PopupWindow): # If we need to run another tournament query, do so. if not self._running_query and ( - (self._last_query_time is None) or (not self._have_valid_data) or - (ba.time(ba.TimeType.REAL) - self._last_query_time > 30.0)): + (self._last_query_time is None) + or (not self._have_valid_data) + or (ba.time(ba.TimeType.REAL) - self._last_query_time > 30.0) + ): ba.internal.tournament_query( args={ - 'source': - 'entry window' if self._tournament_activity is None - else 'retry entry window' + 'source': 'entry window' + if self._tournament_activity is None + else 'retry entry window' }, - callback=ba.WeakCall(self._on_tournament_query_response)) + callback=ba.WeakCall(self._on_tournament_query_response), + ) self._last_query_time = ba.time(ba.TimeType.REAL) self._running_query = True # Grab the latest info on our tourney. self._tournament_info = ba.app.accounts_v1.tournament_info[ - self._tournament_id] + self._tournament_id + ] # If we don't have valid data always show a '-' for time. if not self._have_valid_data: @@ -369,70 +425,106 @@ class TournamentEntryWindow(popup.PopupWindow): else: if self._seconds_remaining is not None: self._seconds_remaining = max(0, self._seconds_remaining - 1) - ba.textwidget(edit=self._time_remaining_text, - text=ba.timestring( - self._seconds_remaining * 1000, - centi=False, - timeformat=ba.TimeFormat.MILLISECONDS)) + ba.textwidget( + edit=self._time_remaining_text, + text=ba.timestring( + self._seconds_remaining * 1000, + centi=False, + timeformat=ba.TimeFormat.MILLISECONDS, + ), + ) # Keep price up-to-date and update the button with it. self._purchase_price = ba.internal.get_v1_account_misc_read_val( - self._purchase_price_name, None) + self._purchase_price_name, None + ) ba.textwidget( edit=self._ticket_cost_text, - text=(ba.Lstr(resource='getTicketsWindow.freeText') - if self._purchase_price == 0 else ba.Lstr( - resource='getTicketsWindow.ticketsText', - subs=[('${COUNT}', str(self._purchase_price) - if self._purchase_price is not None else '?')])), + text=( + ba.Lstr(resource='getTicketsWindow.freeText') + if self._purchase_price == 0 + else ba.Lstr( + resource='getTicketsWindow.ticketsText', + subs=[ + ( + '${COUNT}', + str(self._purchase_price) + if self._purchase_price is not None + else '?', + ) + ], + ) + ), position=self._ticket_cost_text_position_free - if self._purchase_price == 0 else self._ticket_cost_text_position, - scale=1.0 if self._purchase_price == 0 else 0.6) + if self._purchase_price == 0 + else self._ticket_cost_text_position, + scale=1.0 if self._purchase_price == 0 else 0.6, + ) ba.textwidget( edit=self._free_plays_remaining_text, - text='' if - (self._tournament_info['freeTriesRemaining'] in [None, 0] - or self._purchase_price != 0) else '' + - str(self._tournament_info['freeTriesRemaining'])) + text='' + if ( + self._tournament_info['freeTriesRemaining'] in [None, 0] + or self._purchase_price != 0 + ) + else '' + str(self._tournament_info['freeTriesRemaining']), + ) - ba.imagewidget(edit=self._ticket_img, - opacity=0.2 if self._purchase_price == 0 else 1.0, - position=self._ticket_img_pos_free - if self._purchase_price == 0 else self._ticket_img_pos) + ba.imagewidget( + edit=self._ticket_img, + opacity=0.2 if self._purchase_price == 0 else 1.0, + position=self._ticket_img_pos_free + if self._purchase_price == 0 + else self._ticket_img_pos, + ) if self._do_ad_btn: enabled = ba.internal.have_incentivized_ad() have_ad_tries_remaining = ( self._tournament_info['adTriesRemaining'] is not None - and self._tournament_info['adTriesRemaining'] > 0) - ba.textwidget(edit=self._ad_text, - position=self._ad_text_position_remaining if - have_ad_tries_remaining else self._ad_text_position, - color=(0, 1, 0) if enabled else (0.5, 0.5, 0.5)) - ba.imagewidget(edit=self._pay_with_ad_img, - opacity=1.0 if enabled else 0.2) - ba.buttonwidget(edit=self._pay_with_ad_btn, - color=(0.5, 0.7, 0.2) if enabled else - (0.5, 0.5, 0.5)) + and self._tournament_info['adTriesRemaining'] > 0 + ) + ba.textwidget( + edit=self._ad_text, + position=self._ad_text_position_remaining + if have_ad_tries_remaining + else self._ad_text_position, + color=(0, 1, 0) if enabled else (0.5, 0.5, 0.5), + ) + ba.imagewidget( + edit=self._pay_with_ad_img, opacity=1.0 if enabled else 0.2 + ) + ba.buttonwidget( + edit=self._pay_with_ad_btn, + color=(0.5, 0.7, 0.2) if enabled else (0.5, 0.5, 0.5), + ) ad_plays_remaining_text = ( - '' if not have_ad_tries_remaining else '' + - str(self._tournament_info['adTriesRemaining'])) - ba.textwidget(edit=self._ad_plays_remaining_text, - text=ad_plays_remaining_text, - color=(0, 0.8, 0) if enabled else (0.4, 0.4, 0.4)) + '' + if not have_ad_tries_remaining + else '' + str(self._tournament_info['adTriesRemaining']) + ) + ba.textwidget( + edit=self._ad_plays_remaining_text, + text=ad_plays_remaining_text, + color=(0, 0.8, 0) if enabled else (0.4, 0.4, 0.4), + ) try: t_str = str(ba.internal.get_v1_account_ticket_count()) except Exception: t_str = '?' if self._get_tickets_button: - ba.buttonwidget(edit=self._get_tickets_button, - label=ba.charstr(ba.SpecialChar.TICKET) + t_str) + ba.buttonwidget( + edit=self._get_tickets_button, + label=ba.charstr(ba.SpecialChar.TICKET) + t_str, + ) if self._ticket_count_text: - ba.textwidget(edit=self._ticket_count_text, - text=ba.charstr(ba.SpecialChar.TICKET) + t_str) + ba.textwidget( + edit=self._ticket_count_text, + text=ba.charstr(ba.SpecialChar.TICKET) + t_str, + ) def _launch(self) -> None: if self._launched: @@ -443,17 +535,23 @@ class TournamentEntryWindow(popup.PopupWindow): # If they gave us an existing activity, just restart it. if self._tournament_activity is not None: try: - ba.timer(0.1, - lambda: ba.playsound(ba.getsound('cashRegister')), - timetype=ba.TimeType.REAL) + ba.timer( + 0.1, + lambda: ba.playsound(ba.getsound('cashRegister')), + timetype=ba.TimeType.REAL, + ) with ba.Context(self._tournament_activity): - self._tournament_activity.end({'outcome': 'restart'}, - force=True) + self._tournament_activity.end( + {'outcome': 'restart'}, force=True + ) ba.timer(0.3, self._transition_out, timetype=ba.TimeType.REAL) launched = True - ba.screenmessage(ba.Lstr(translate=('serverResponses', - 'Entering tournament...')), - color=(0, 1, 0)) + ba.screenmessage( + ba.Lstr( + translate=('serverResponses', 'Entering tournament...') + ), + color=(0, 1, 0), + ) # We can hit exceptions here if _tournament_activity ends before # our restart attempt happens. @@ -466,9 +564,11 @@ class TournamentEntryWindow(popup.PopupWindow): # If we had no existing activity (or were unable to restart it) # launch a new session. if not launched: - ba.timer(0.1, - lambda: ba.playsound(ba.getsound('cashRegister')), - timetype=ba.TimeType.REAL) + ba.timer( + 0.1, + lambda: ba.playsound(ba.getsound('cashRegister')), + timetype=ba.TimeType.REAL, + ) ba.timer( 1.0, lambda: ba.app.launch_coop_game( @@ -476,13 +576,18 @@ class TournamentEntryWindow(popup.PopupWindow): args={ 'min_players': self._tournament_info['minPlayers'], 'max_players': self._tournament_info['maxPlayers'], - 'tournament_id': self._tournament_id - }), - timetype=ba.TimeType.REAL) + 'tournament_id': self._tournament_id, + }, + ), + timetype=ba.TimeType.REAL, + ) ba.timer(0.7, self._transition_out, timetype=ba.TimeType.REAL) - ba.screenmessage(ba.Lstr(translate=('serverResponses', - 'Entering tournament...')), - color=(0, 1, 0)) + ba.screenmessage( + ba.Lstr( + translate=('serverResponses', 'Entering tournament...') + ), + color=(0, 1, 0), + ) def _on_pay_with_tickets_press(self) -> None: from bastd.ui import getcurrency @@ -492,22 +597,25 @@ class TournamentEntryWindow(popup.PopupWindow): return if not self._have_valid_data: - ba.screenmessage(ba.Lstr(resource='tournamentCheckingStateText'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr(resource='tournamentCheckingStateText'), color=(1, 0, 0) + ) ba.playsound(ba.getsound('error')) return # If we don't have a price. if self._purchase_price is None: - ba.screenmessage(ba.Lstr(resource='tournamentCheckingStateText'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr(resource='tournamentCheckingStateText'), color=(1, 0, 0) + ) ba.playsound(ba.getsound('error')) return # Deny if it looks like the tourney has ended. if self._seconds_remaining == 0: - ba.screenmessage(ba.Lstr(resource='tournamentEndedText'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr(resource='tournamentEndedText'), color=(1, 0, 0) + ) ba.playsound(ba.getsound('error')) return @@ -522,6 +630,7 @@ class TournamentEntryWindow(popup.PopupWindow): if ticket_count is not None and ticket_count < ticket_cost: getcurrency.show_get_tickets_prompt() ba.playsound(ba.getsound('error')) + self._transition_out() return cur_time = ba.time(ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS) @@ -530,11 +639,13 @@ class TournamentEntryWindow(popup.PopupWindow): ba.internal.in_game_purchase(self._purchase_name, ticket_cost) self._entering = True - ba.internal.add_transaction({ - 'type': 'ENTER_TOURNAMENT', - 'fee': self._fee, - 'tournamentID': self._tournament_id - }) + ba.internal.add_transaction( + { + 'type': 'ENTER_TOURNAMENT', + 'fee': self._fee, + 'tournamentID': self._tournament_id, + } + ) ba.internal.run_transactions() self._launch() @@ -545,24 +656,27 @@ class TournamentEntryWindow(popup.PopupWindow): return if not self._have_valid_data: - ba.screenmessage(ba.Lstr(resource='tournamentCheckingStateText'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr(resource='tournamentCheckingStateText'), color=(1, 0, 0) + ) ba.playsound(ba.getsound('error')) return # Deny if it looks like the tourney has ended. if self._seconds_remaining == 0: - ba.screenmessage(ba.Lstr(resource='tournamentEndedText'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr(resource='tournamentEndedText'), color=(1, 0, 0) + ) ba.playsound(ba.getsound('error')) return cur_time = ba.time(ba.TimeType.REAL) if cur_time - self._last_ad_press_time > 5.0: self._last_ad_press_time = cur_time - ba.app.ads.show_ad_2('tournament_entry', - on_completion_call=ba.WeakCall( - self._on_ad_complete)) + ba.app.ads.show_ad_2( + 'tournament_entry', + on_completion_call=ba.WeakCall(self._on_ad_complete), + ) def _on_ad_complete(self, actually_showed: bool) -> None: @@ -587,11 +701,13 @@ class TournamentEntryWindow(popup.PopupWindow): return self._entering = True - ba.internal.add_transaction({ - 'type': 'ENTER_TOURNAMENT', - 'fee': 'ad', - 'tournamentID': self._tournament_id - }) + ba.internal.add_transaction( + { + 'type': 'ENTER_TOURNAMENT', + 'fee': 'ad', + 'tournamentID': self._tournament_id, + } + ) ba.internal.run_transactions() self._launch() @@ -604,8 +720,9 @@ class TournamentEntryWindow(popup.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(modal=True, - origin_widget=self._get_tickets_button) + getcurrency.GetCurrencyWindow( + modal=True, origin_widget=self._get_tickets_button + ) self._transition_out() def _on_cancel(self) -> None: @@ -613,11 +730,15 @@ class TournamentEntryWindow(popup.PopupWindow): # Don't allow canceling for several seconds after poking an enter # button if it looks like we're waiting on a purchase or entering # the tournament. - if ((ba.time(ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS) - - self._last_ticket_press_time < 6000) - and (ba.internal.have_outstanding_transactions() - or ba.internal.get_purchased(self._purchase_name) - or self._entering)): + if ( + ba.time(ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS) + - self._last_ticket_press_time + < 6000 + ) and ( + ba.internal.have_outstanding_transactions() + or ba.internal.get_purchased(self._purchase_name) + or self._entering + ): ba.playsound(ba.getsound('error')) return self._transition_out() diff --git a/dist/ba_data/python/bastd/ui/tournamentscores.py b/dist/ba_data/python/bastd/ui/tournamentscores.py index 3662512..418f578 100644 --- a/dist/ba_data/python/bastd/ui/tournamentscores.py +++ b/dist/ba_data/python/bastd/ui/tournamentscores.py @@ -17,16 +17,18 @@ if TYPE_CHECKING: class TournamentScoresWindow(popup_ui.PopupWindow): """Window for viewing tournament scores.""" - def __init__(self, - tournament_id: str, - tournament_activity: ba.GameActivity | None = None, - position: tuple[float, float] = (0.0, 0.0), - scale: float | None = None, - offset: tuple[float, float] = (0.0, 0.0), - tint_color: Sequence[float] = (1.0, 1.0, 1.0), - tint2_color: Sequence[float] = (1.0, 1.0, 1.0), - selected_character: str | None = None, - on_close_call: Callable[[], Any] | None = None): + def __init__( + self, + tournament_id: str, + tournament_activity: ba.GameActivity | None = None, + position: tuple[float, float] = (0.0, 0.0), + scale: float | None = None, + offset: tuple[float, float] = (0.0, 0.0), + tint_color: Sequence[float] = (1.0, 1.0, 1.0), + tint2_color: Sequence[float] = (1.0, 1.0, 1.0), + selected_character: str | None = None, + on_close_call: Callable[[], Any] | None = None, + ): del tournament_activity # unused arg del tint_color # unused arg @@ -37,22 +39,34 @@ class TournamentScoresWindow(popup_ui.PopupWindow): self._on_close_call = on_close_call uiscale = ba.app.ui.uiscale if scale is None: - scale = (2.3 if uiscale is ba.UIScale.SMALL else - 1.65 if uiscale is ba.UIScale.MEDIUM else 1.23) + scale = ( + 2.3 + if uiscale is ba.UIScale.SMALL + else 1.65 + if uiscale is ba.UIScale.MEDIUM + else 1.23 + ) self._transitioning_out = False self._width = 400 - self._height = (300 if uiscale is ba.UIScale.SMALL else - 370 if uiscale is ba.UIScale.MEDIUM else 450) + self._height = ( + 300 + if uiscale is ba.UIScale.SMALL + else 370 + if uiscale is ba.UIScale.MEDIUM + else 450 + ) bg_color = (0.5, 0.4, 0.6) # creates our _root_widget - super().__init__(position=position, - size=(self._width, self._height), - scale=scale, - bg_color=bg_color, - offset=offset) + super().__init__( + position=position, + size=(self._width, self._height), + scale=scale, + bg_color=bg_color, + offset=offset, + ) # app = ba.app @@ -66,7 +80,8 @@ class TournamentScoresWindow(popup_ui.PopupWindow): on_activate_call=self._on_cancel_press, autoselect=True, icon=ba.gettexture('crossOut'), - iconscale=1.2) + iconscale=1.2, + ) self._title_text = ba.textwidget( parent=self.root_widget, @@ -77,38 +92,46 @@ class TournamentScoresWindow(popup_ui.PopupWindow): scale=0.6, text=ba.Lstr(resource='tournamentStandingsText'), maxwidth=200, - color=(1, 1, 1, 0.4)) + color=(1, 1, 1, 0.4), + ) - self._scrollwidget = ba.scrollwidget(parent=self.root_widget, - size=(self._width - 60, - self._height - 70), - position=(30, 30), - highlight=False, - simple_culling_v=10) + self._scrollwidget = ba.scrollwidget( + parent=self.root_widget, + size=(self._width - 60, self._height - 70), + position=(30, 30), + highlight=False, + simple_culling_v=10, + ) ba.widget(edit=self._scrollwidget, autoselect=True) self._loading_text = ba.textwidget( parent=self._scrollwidget, scale=0.5, - text=ba.Lstr(value='${A}...', - subs=[('${A}', ba.Lstr(resource='loadingText'))]), + text=ba.Lstr( + value='${A}...', + subs=[('${A}', ba.Lstr(resource='loadingText'))], + ), size=(self._width - 60, 100), h_align='center', - v_align='center') + v_align='center', + ) - ba.containerwidget(edit=self.root_widget, - cancel_button=self._cancel_button) + ba.containerwidget( + edit=self.root_widget, cancel_button=self._cancel_button + ) - ba.internal.tournament_query(args={ - 'tournamentIDs': [tournament_id], - 'numScores': 50, - 'source': 'scores window' - }, - callback=ba.WeakCall( - self._on_tournament_query_response)) + ba.internal.tournament_query( + args={ + 'tournamentIDs': [tournament_id], + 'numScores': 50, + 'source': 'scores window', + }, + callback=ba.WeakCall(self._on_tournament_query_response), + ) - def _on_tournament_query_response(self, - data: dict[str, Any] | None) -> None: + def _on_tournament_query_response( + self, data: dict[str, Any] | None + ) -> None: if data is not None: # this used to be the whole payload data_t: list[dict[str, Any]] = data['t'] @@ -117,52 +140,62 @@ class TournamentScoresWindow(popup_ui.PopupWindow): if data_t[0]['scores']: self._loading_text.delete() else: - ba.textwidget(edit=self._loading_text, - text=ba.Lstr(resource='noScoresYetText')) + ba.textwidget( + edit=self._loading_text, + text=ba.Lstr(resource='noScoresYetText'), + ) incr = 30 sub_width = self._width - 90 sub_height = 30 + len(data_t[0]['scores']) * incr - self._subcontainer = ba.containerwidget(parent=self._scrollwidget, - size=(sub_width, - sub_height), - background=False) + self._subcontainer = ba.containerwidget( + parent=self._scrollwidget, + size=(sub_width, sub_height), + background=False, + ) for i, entry in enumerate(data_t[0]['scores']): - ba.textwidget(parent=self._subcontainer, - position=(sub_width * 0.1 - 5, - sub_height - 20 - incr * i), - maxwidth=20, - scale=0.5, - color=(0.6, 0.6, 0.7), - flatness=1.0, - shadow=0.0, - text=str(i + 1), - size=(0, 0), - h_align='right', - v_align='center') - ba.textwidget( parent=self._subcontainer, - position=(sub_width * 0.25 - 2, - sub_height - 20 - incr * i), + position=(sub_width * 0.1 - 5, sub_height - 20 - incr * i), + maxwidth=20, + scale=0.5, + color=(0.6, 0.6, 0.7), + flatness=1.0, + shadow=0.0, + text=str(i + 1), + size=(0, 0), + h_align='right', + v_align='center', + ) + + ba.textwidget( + parent=self._subcontainer, + position=(sub_width * 0.25 - 2, sub_height - 20 - incr * i), maxwidth=sub_width * 0.24, color=(0.9, 1.0, 0.9), flatness=1.0, shadow=0.0, scale=0.6, - text=(ba.timestring(entry[0] * 10, - centi=True, - timeformat=ba.TimeFormat.MILLISECONDS) - if data_t[0]['scoreType'] == 'time' else str( - entry[0])), + text=( + ba.timestring( + entry[0] * 10, + centi=True, + timeformat=ba.TimeFormat.MILLISECONDS, + ) + if data_t[0]['scoreType'] == 'time' + else str(entry[0]) + ), size=(0, 0), h_align='center', - v_align='center') + v_align='center', + ) txt = ba.textwidget( parent=self._subcontainer, - position=(sub_width * 0.25, - sub_height - 20 - incr * i - (0.5 / 0.7) * incr), + position=( + sub_width * 0.25, + sub_height - 20 - incr * i - (0.5 / 0.7) * incr, + ), maxwidth=sub_width * 0.6, scale=0.7, flatness=1.0, @@ -174,24 +207,31 @@ class TournamentScoresWindow(popup_ui.PopupWindow): extra_touch_border_scale=0.0, size=((sub_width * 0.6) / 0.7, incr / 0.7), h_align='left', - v_align='center') + v_align='center', + ) - ba.textwidget(edit=txt, - on_activate_call=ba.Call(self._show_player_info, - entry, txt)) + ba.textwidget( + edit=txt, + on_activate_call=ba.Call( + self._show_player_info, entry, txt + ), + ) if i == 0: ba.widget(edit=txt, up_widget=self._cancel_button) def _show_player_info(self, entry: Any, textwidget: ba.Widget) -> None: from bastd.ui.account.viewer import AccountViewerWindow + # for the moment we only work if a single player-info is present.. if len(entry[2]) != 1: ba.playsound(ba.getsound('error')) return ba.playsound(ba.getsound('swish')) - AccountViewerWindow(account_id=entry[2][0].get('a', None), - profile_id=entry[2][0].get('p', None), - position=textwidget.get_screen_space_center()) + AccountViewerWindow( + account_id=entry[2][0].get('a', None), + profile_id=entry[2][0].get('p', None), + position=textwidget.get_screen_space_center(), + ) self._transition_out() def _on_cancel_press(self) -> None: diff --git a/dist/ba_data/python/bastd/ui/trophies.py b/dist/ba_data/python/bastd/ui/trophies.py index ec17007..f263cc2 100644 --- a/dist/ba_data/python/bastd/ui/trophies.py +++ b/dist/ba_data/python/bastd/ui/trophies.py @@ -16,25 +16,34 @@ if TYPE_CHECKING: class TrophiesWindow(popup.PopupWindow): """Popup window for viewing trophies.""" - def __init__(self, - position: tuple[float, float], - data: dict[str, Any], - scale: float | None = None): + def __init__( + self, + position: tuple[float, float], + data: dict[str, Any], + scale: float | None = None, + ): self._data = data uiscale = ba.app.ui.uiscale if scale is None: - scale = (2.3 if uiscale is ba.UIScale.SMALL else - 1.65 if uiscale is ba.UIScale.MEDIUM else 1.23) + scale = ( + 2.3 + if uiscale is ba.UIScale.SMALL + else 1.65 + if uiscale is ba.UIScale.MEDIUM + else 1.23 + ) self._transitioning_out = False self._width = 300 self._height = 300 bg_color = (0.5, 0.4, 0.6) - popup.PopupWindow.__init__(self, - position=position, - size=(self._width, self._height), - scale=scale, - bg_color=bg_color) + popup.PopupWindow.__init__( + self, + position=position, + size=(self._width, self._height), + scale=scale, + bg_color=bg_color, + ) self._cancel_button = ba.buttonwidget( parent=self.root_widget, @@ -46,28 +55,32 @@ class TrophiesWindow(popup.PopupWindow): on_activate_call=self._on_cancel_press, autoselect=True, icon=ba.gettexture('crossOut'), - iconscale=1.2) + iconscale=1.2, + ) - self._title_text = ba.textwidget(parent=self.root_widget, - position=(self._width * 0.5, - self._height - 20), - size=(0, 0), - h_align='center', - v_align='center', - scale=0.6, - text=ba.Lstr(resource='trophiesText'), - maxwidth=200, - color=(1, 1, 1, 0.4)) + self._title_text = ba.textwidget( + parent=self.root_widget, + position=(self._width * 0.5, self._height - 20), + size=(0, 0), + h_align='center', + v_align='center', + scale=0.6, + text=ba.Lstr(resource='trophiesText'), + maxwidth=200, + color=(1, 1, 1, 0.4), + ) - self._scrollwidget = ba.scrollwidget(parent=self.root_widget, - size=(self._width - 60, - self._height - 70), - position=(30, 30), - capture_arrows=True) + self._scrollwidget = ba.scrollwidget( + parent=self.root_widget, + size=(self._width - 60, self._height - 70), + position=(30, 30), + capture_arrows=True, + ) ba.widget(edit=self._scrollwidget, autoselect=True) - ba.containerwidget(edit=self.root_widget, - cancel_button=self._cancel_button) + ba.containerwidget( + edit=self.root_widget, cancel_button=self._cancel_button + ) incr = 31 sub_width = self._width - 90 @@ -76,101 +89,119 @@ class TrophiesWindow(popup.PopupWindow): sub_height = 40 + len(trophy_types) * incr eq_text = ba.Lstr( - resource='coopSelectWindow.powerRankingPointsEqualsText').evaluate( - ) + resource='coopSelectWindow.powerRankingPointsEqualsText' + ).evaluate() - self._subcontainer = ba.containerwidget(parent=self._scrollwidget, - size=(sub_width, sub_height), - background=False) + self._subcontainer = ba.containerwidget( + parent=self._scrollwidget, + size=(sub_width, sub_height), + background=False, + ) total_pts = 0 multi_txt = ba.Lstr( - resource='coopSelectWindow.powerRankingPointsMultText').evaluate() + resource='coopSelectWindow.powerRankingPointsMultText' + ).evaluate() - total_pts += self._create_trophy_type_widgets(eq_text, incr, multi_txt, - sub_height, sub_width, - trophy_types) + total_pts += self._create_trophy_type_widgets( + eq_text, incr, multi_txt, sub_height, sub_width, trophy_types + ) ba.textwidget( parent=self._subcontainer, - position=(sub_width * 1.0, - sub_height - 20 - incr * len(trophy_types)), + position=( + sub_width * 1.0, + sub_height - 20 - incr * len(trophy_types), + ), maxwidth=sub_width * 0.5, scale=0.7, color=(0.7, 0.8, 1.0), flatness=1.0, shadow=0.0, - text=ba.Lstr(resource='coopSelectWindow.totalText').evaluate() + - ' ' + eq_text.replace('${NUMBER}', str(total_pts)), + text=ba.Lstr(resource='coopSelectWindow.totalText').evaluate() + + ' ' + + eq_text.replace('${NUMBER}', str(total_pts)), size=(0, 0), h_align='right', - v_align='center') + v_align='center', + ) - def _create_trophy_type_widgets(self, eq_text: str, incr: int, - multi_txt: str, sub_height: int, - sub_width: int, - trophy_types: list[list[str]]) -> int: + def _create_trophy_type_widgets( + self, + eq_text: str, + incr: int, + multi_txt: str, + sub_height: int, + sub_width: int, + trophy_types: list[list[str]], + ) -> int: from ba.internal import get_trophy_string + total_pts = 0 for i, trophy_type in enumerate(trophy_types): t_count = self._data['t' + trophy_type[0]] t_mult = self._data['t' + trophy_type[0] + 'm'] - ba.textwidget(parent=self._subcontainer, - position=(sub_width * 0.15, - sub_height - 20 - incr * i), - scale=0.7, - flatness=1.0, - shadow=0.7, - color=(1, 1, 1), - text=get_trophy_string(trophy_type[0]), - size=(0, 0), - h_align='center', - v_align='center') + ba.textwidget( + parent=self._subcontainer, + position=(sub_width * 0.15, sub_height - 20 - incr * i), + scale=0.7, + flatness=1.0, + shadow=0.7, + color=(1, 1, 1), + text=get_trophy_string(trophy_type[0]), + size=(0, 0), + h_align='center', + v_align='center', + ) - ba.textwidget(parent=self._subcontainer, - position=(sub_width * 0.31, - sub_height - 20 - incr * i), - maxwidth=sub_width * 0.2, - scale=0.8, - flatness=1.0, - shadow=0.0, - color=(0, 1, 0) if (t_count > 0) else - (0.6, 0.6, 0.6, 0.5), - text=str(t_count), - size=(0, 0), - h_align='center', - v_align='center') + ba.textwidget( + parent=self._subcontainer, + position=(sub_width * 0.31, sub_height - 20 - incr * i), + maxwidth=sub_width * 0.2, + scale=0.8, + flatness=1.0, + shadow=0.0, + color=(0, 1, 0) if (t_count > 0) else (0.6, 0.6, 0.6, 0.5), + text=str(t_count), + size=(0, 0), + h_align='center', + v_align='center', + ) txt = multi_txt.replace('${NUMBER}', str(t_mult)) - ba.textwidget(parent=self._subcontainer, - position=(sub_width * 0.57, - sub_height - 20 - incr * i), - maxwidth=sub_width * 0.3, - scale=0.4, - flatness=1.0, - shadow=0.0, - color=(0.63, 0.6, 0.75) if (t_count > 0) else - (0.6, 0.6, 0.6, 0.4), - text=txt, - size=(0, 0), - h_align='center', - v_align='center') + ba.textwidget( + parent=self._subcontainer, + position=(sub_width * 0.57, sub_height - 20 - incr * i), + maxwidth=sub_width * 0.3, + scale=0.4, + flatness=1.0, + shadow=0.0, + color=(0.63, 0.6, 0.75) + if (t_count > 0) + else (0.6, 0.6, 0.6, 0.4), + text=txt, + size=(0, 0), + h_align='center', + v_align='center', + ) this_pts = t_count * t_mult - ba.textwidget(parent=self._subcontainer, - position=(sub_width * 0.88, - sub_height - 20 - incr * i), - maxwidth=sub_width * 0.3, - color=(0.7, 0.8, 1.0) if (t_count > 0) else - (0.9, 0.9, 1.0, 0.3), - flatness=1.0, - shadow=0.0, - scale=0.5, - text=eq_text.replace('${NUMBER}', str(this_pts)), - size=(0, 0), - h_align='center', - v_align='center') + ba.textwidget( + parent=self._subcontainer, + position=(sub_width * 0.88, sub_height - 20 - incr * i), + maxwidth=sub_width * 0.3, + color=(0.7, 0.8, 1.0) + if (t_count > 0) + else (0.9, 0.9, 1.0, 0.3), + flatness=1.0, + shadow=0.0, + scale=0.5, + text=eq_text.replace('${NUMBER}', str(this_pts)), + size=(0, 0), + h_align='center', + v_align='center', + ) total_pts += this_pts return total_pts diff --git a/dist/ba_data/python/bastd/ui/url.py b/dist/ba_data/python/bastd/ui/url.py index fe90fb3..bcecbb9 100644 --- a/dist/ba_data/python/bastd/ui/url.py +++ b/dist/ba_data/python/bastd/ui/url.py @@ -17,73 +17,93 @@ class ShowURLWindow(ba.Window): # (for long URLs especially) app = ba.app uiscale = app.ui.uiscale - if app.platform == 'android' and app.subplatform == 'alibaba': - self._width = 500 - self._height = 500 - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height), - transition='in_right', - scale=(1.25 if uiscale is ba.UIScale.SMALL else - 1.25 if uiscale is ba.UIScale.MEDIUM else 1.25))) - self._cancel_button = ba.buttonwidget( - parent=self._root_widget, - position=(50, self._height - 30), - size=(50, 50), - scale=0.6, - label='', - color=(0.6, 0.5, 0.6), - on_activate_call=self._done, - autoselect=True, - icon=ba.gettexture('crossOut'), - iconscale=1.2) - qr_size = 400 - ba.imagewidget(parent=self._root_widget, - position=(self._width * 0.5 - qr_size * 0.5, - self._height * 0.5 - qr_size * 0.5), - size=(qr_size, qr_size), - texture=ba.internal.get_qrcode_texture(address)) - ba.containerwidget(edit=self._root_widget, - cancel_button=self._cancel_button) - else: - # show it as a simple string... - self._width = 800 - self._height = 200 - self._root_widget = ba.containerwidget( + self._address = address + + self._width = 800 + self._height = 450 + super().__init__( + root_widget=ba.containerwidget( size=(self._width, self._height + 40), transition='in_right', - scale=(1.25 if uiscale is ba.UIScale.SMALL else - 1.25 if uiscale is ba.UIScale.MEDIUM else 1.25)) - ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, self._height - 10), - size=(0, 0), - color=ba.app.ui.title_color, - h_align='center', - v_align='center', - text=ba.Lstr(resource='directBrowserToURLText'), - maxwidth=self._width * 0.95) - ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, - self._height * 0.5 + 29), - size=(0, 0), - scale=1.3, - color=ba.app.ui.infotextcolor, - h_align='center', - v_align='center', - text=address, - maxwidth=self._width * 0.95) - button_width = 200 - btn = ba.buttonwidget(parent=self._root_widget, - position=(self._width * 0.5 - - button_width * 0.5, 20), - size=(button_width, 65), - label=ba.Lstr(resource='doneText'), - on_activate_call=self._done) - # we have no 'cancel' button but still want to be able to - # hit back/escape/etc to leave.. - ba.containerwidget(edit=self._root_widget, - selected_child=btn, - start_button=btn, - on_cancel_call=btn.activate) + scale=( + 1.25 + if uiscale is ba.UIScale.SMALL + else 1.25 + if uiscale is ba.UIScale.MEDIUM + else 1.25 + ), + ) + ) + ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, self._height - 10), + size=(0, 0), + color=ba.app.ui.title_color, + h_align='center', + v_align='center', + text=ba.Lstr(resource='directBrowserToURLText'), + maxwidth=self._width * 0.95, + ) + ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, self._height - 60), + size=(0, 0), + scale=1.3, + color=ba.app.ui.infotextcolor, + h_align='center', + v_align='center', + text=address, + maxwidth=self._width * 0.95, + ) + button_width = 200 + + qr_size = 220 + ba.imagewidget( + parent=self._root_widget, + position=( + self._width * 0.5 - qr_size * 0.5, + self._height * 0.5 - qr_size * 0.5 + 10, + ), + size=(qr_size, qr_size), + texture=ba.internal.get_qrcode_texture(address), + ) + + xoffs = 0 + if ba.clipboard_is_supported(): + xoffs = -150 + btn = ba.buttonwidget( + parent=self._root_widget, + position=( + self._width * 0.5 - button_width * 0.5 + xoffs, + 20, + ), + size=(button_width, 65), + autoselect=True, + label=ba.Lstr(resource='copyText'), + on_activate_call=self._copy, + ) + xoffs = 150 + + btn = ba.buttonwidget( + parent=self._root_widget, + position=(self._width * 0.5 - button_width * 0.5 + xoffs, 20), + size=(button_width, 65), + autoselect=True, + label=ba.Lstr(resource='doneText'), + on_activate_call=self._done, + ) + # we have no 'cancel' button but still want to be able to + # hit back/escape/etc to leave.. + ba.containerwidget( + edit=self._root_widget, + selected_child=btn, + start_button=btn, + on_cancel_call=btn.activate, + ) + + def _copy(self) -> None: + ba.clipboard_set_text(self._address) + ba.screenmessage(ba.Lstr(resource='copyConfirmText'), color=(0, 1, 0)) def _done(self) -> None: ba.containerwidget(edit=self._root_widget, transition='out_left') diff --git a/dist/ba_data/python/bastd/ui/watch.py b/dist/ba_data/python/bastd/ui/watch.py index eca946b..85d484e 100644 --- a/dist/ba_data/python/bastd/ui/watch.py +++ b/dist/ba_data/python/bastd/ui/watch.py @@ -20,15 +20,19 @@ class WatchWindow(ba.Window): class TabID(Enum): """Our available tab types.""" + MY_REPLAYS = 'my_replays' TEST_TAB = 'test_tab' - def __init__(self, - transition: str | None = 'in_right', - origin_widget: ba.Widget | None = None): + def __init__( + self, + transition: str | None = 'in_right', + origin_widget: ba.Widget | None = None, + ): # pylint: disable=too-many-locals # pylint: disable=too-many-statements from bastd.ui.tabs import TabRow + ba.set_analytics_screen('Watch Window') scale_origin: tuple[float, float] | None if origin_widget is not None: @@ -51,24 +55,41 @@ class WatchWindow(ba.Window): uiscale = ba.app.ui.uiscale self._width = 1240 if uiscale is ba.UIScale.SMALL else 1040 x_inset = 100 if uiscale is ba.UIScale.SMALL else 0 - self._height = (578 if uiscale is ba.UIScale.SMALL else - 670 if uiscale is ba.UIScale.MEDIUM else 800) + self._height = ( + 578 + if uiscale is ba.UIScale.SMALL + else 670 + if uiscale is ba.UIScale.MEDIUM + else 800 + ) self._current_tab: WatchWindow.TabID | None = None extra_top = 20 if uiscale is ba.UIScale.SMALL else 0 - super().__init__(root_widget=ba.containerwidget( - size=(self._width, self._height + extra_top), - transition=transition, - toolbar_visibility='menu_minimal', - scale_origin_stack_offset=scale_origin, - scale=(1.3 if uiscale is ba.UIScale.SMALL else - 0.97 if uiscale is ba.UIScale.MEDIUM else 0.8), - stack_offset=(0, -10) if uiscale is ba.UIScale.SMALL else ( - 0, 15) if uiscale is ba.UIScale.MEDIUM else (0, 0))) + super().__init__( + root_widget=ba.containerwidget( + size=(self._width, self._height + extra_top), + transition=transition, + toolbar_visibility='menu_minimal', + scale_origin_stack_offset=scale_origin, + scale=( + 1.3 + if uiscale is ba.UIScale.SMALL + else 0.97 + if uiscale is ba.UIScale.MEDIUM + else 0.8 + ), + stack_offset=(0, -10) + if uiscale is ba.UIScale.SMALL + else (0, 15) + if uiscale is ba.UIScale.MEDIUM + else (0, 0), + ) + ) if uiscale is ba.UIScale.SMALL and ba.app.ui.use_toolbars: - ba.containerwidget(edit=self._root_widget, - on_cancel_call=self._back) + ba.containerwidget( + edit=self._root_widget, on_cancel_call=self._back + ) self._back_button = None else: self._back_button = btn = ba.buttonwidget( @@ -79,49 +100,59 @@ class WatchWindow(ba.Window): scale=1.1, label=ba.Lstr(resource='backText'), button_type='back', - on_activate_call=self._back) + on_activate_call=self._back, + ) ba.containerwidget(edit=self._root_widget, cancel_button=btn) - ba.buttonwidget(edit=btn, - button_type='backSmall', - size=(60, 60), - label=ba.charstr(ba.SpecialChar.BACK)) + ba.buttonwidget( + edit=btn, + button_type='backSmall', + size=(60, 60), + label=ba.charstr(ba.SpecialChar.BACK), + ) - ba.textwidget(parent=self._root_widget, - position=(self._width * 0.5, self._height - 38), - size=(0, 0), - color=ba.app.ui.title_color, - scale=1.5, - h_align='center', - v_align='center', - text=ba.Lstr(resource=self._r + '.titleText'), - maxwidth=400) + ba.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, self._height - 38), + size=(0, 0), + color=ba.app.ui.title_color, + scale=1.5, + h_align='center', + v_align='center', + text=ba.Lstr(resource=self._r + '.titleText'), + maxwidth=400, + ) tabdefs = [ - (self.TabID.MY_REPLAYS, - ba.Lstr(resource=self._r + '.myReplaysText')), + ( + self.TabID.MY_REPLAYS, + ba.Lstr(resource=self._r + '.myReplaysText'), + ), # (self.TabID.TEST_TAB, ba.Lstr(value='Testing')), ] scroll_buffer_h = 130 + 2 * x_inset tab_buffer_h = 750 + 2 * x_inset - self._tab_row = TabRow(self._root_widget, - tabdefs, - pos=(tab_buffer_h * 0.5, self._height - 130), - size=(self._width - tab_buffer_h, 50), - on_select_call=self._set_tab) + self._tab_row = TabRow( + self._root_widget, + tabdefs, + pos=(tab_buffer_h * 0.5, self._height - 130), + size=(self._width - tab_buffer_h, 50), + on_select_call=self._set_tab, + ) if ba.app.ui.use_toolbars: first_tab = self._tab_row.tabs[tabdefs[0][0]] last_tab = self._tab_row.tabs[tabdefs[-1][0]] ba.widget( edit=last_tab.button, - right_widget=ba.internal.get_special_widget('party_button')) + right_widget=ba.internal.get_special_widget('party_button'), + ) if uiscale is ba.UIScale.SMALL: bbtn = ba.internal.get_special_widget('back_button') - ba.widget(edit=first_tab.button, - up_widget=bbtn, - left_widget=bbtn) + ba.widget( + edit=first_tab.button, up_widget=bbtn, left_widget=bbtn + ) self._scroll_width = self._width - scroll_buffer_h self._scroll_height = self._height - 180 @@ -131,13 +162,16 @@ class WatchWindow(ba.Window): scroll_bottom = self._height - self._scroll_height - 79 - 48 buffer_h = 10 buffer_v = 4 - ba.imagewidget(parent=self._root_widget, - position=(scroll_left - buffer_h, - scroll_bottom - buffer_v), - size=(self._scroll_width + 2 * buffer_h, - self._scroll_height + 2 * buffer_v), - texture=ba.gettexture('scrollWidget'), - model_transparent=ba.getmodel('softEdgeOutside')) + ba.imagewidget( + parent=self._root_widget, + position=(scroll_left - buffer_h, scroll_bottom - buffer_v), + size=( + self._scroll_width + 2 * buffer_h, + self._scroll_height + 2 * buffer_v, + ), + texture=ba.gettexture('scrollWidget'), + model_transparent=ba.getmodel('softEdgeOutside'), + ) self._tab_container: ba.Widget | None = None self._restore_state() @@ -173,42 +207,67 @@ class WatchWindow(ba.Window): c_height = self._scroll_height - 20 sub_scroll_height = c_height - 63 self._my_replays_scroll_width = sub_scroll_width = ( - 680 if uiscale is ba.UIScale.SMALL else 640) + 680 if uiscale is ba.UIScale.SMALL else 640 + ) self._tab_container = cnt = ba.containerwidget( parent=self._root_widget, - position=(scroll_left, scroll_bottom + - (self._scroll_height - c_height) * 0.5), + position=( + scroll_left, + scroll_bottom + (self._scroll_height - c_height) * 0.5, + ), size=(c_width, c_height), background=False, - selection_loops_to_parent=True) + selection_loops_to_parent=True, + ) v = c_height - 30 - ba.textwidget(parent=cnt, - position=(c_width * 0.5, v), - color=(0.6, 1.0, 0.6), - scale=0.7, - size=(0, 0), - maxwidth=c_width * 0.9, - h_align='center', - v_align='center', - text=ba.Lstr( - resource='replayRenameWarningText', - subs=[('${REPLAY}', - ba.Lstr(resource='replayNameDefaultText')) - ])) + ba.textwidget( + parent=cnt, + position=(c_width * 0.5, v), + color=(0.6, 1.0, 0.6), + scale=0.7, + size=(0, 0), + maxwidth=c_width * 0.9, + h_align='center', + v_align='center', + text=ba.Lstr( + resource='replayRenameWarningText', + subs=[ + ('${REPLAY}', ba.Lstr(resource='replayNameDefaultText')) + ], + ), + ) b_width = 140 if uiscale is ba.UIScale.SMALL else 178 - b_height = (107 if uiscale is ba.UIScale.SMALL else - 142 if uiscale is ba.UIScale.MEDIUM else 190) - b_space_extra = (0 if uiscale is ba.UIScale.SMALL else - -2 if uiscale is ba.UIScale.MEDIUM else -5) + b_height = ( + 107 + if uiscale is ba.UIScale.SMALL + else 142 + if uiscale is ba.UIScale.MEDIUM + else 190 + ) + b_space_extra = ( + 0 + if uiscale is ba.UIScale.SMALL + else -2 + if uiscale is ba.UIScale.MEDIUM + else -5 + ) b_color = (0.6, 0.53, 0.63) b_textcolor = (0.75, 0.7, 0.8) - btnv = (c_height - (48 if uiscale is ba.UIScale.SMALL else - 45 if uiscale is ba.UIScale.MEDIUM else 40) - - b_height) + btnv = ( + c_height + - ( + 48 + if uiscale is ba.UIScale.SMALL + else 45 + if uiscale is ba.UIScale.MEDIUM + else 40 + ) + - b_height + ) btnh = 40 if uiscale is ba.UIScale.SMALL else 40 smlh = 190 if uiscale is ba.UIScale.SMALL else 225 tscl = 1.0 if uiscale is ba.UIScale.SMALL else 1.2 @@ -222,62 +281,68 @@ class WatchWindow(ba.Window): on_activate_call=self._on_my_replay_play_press, text_scale=tscl, label=ba.Lstr(resource=self._r + '.watchReplayButtonText'), - autoselect=True) + autoselect=True, + ) ba.widget(edit=btn1, up_widget=self._tab_row.tabs[tab_id].button) if uiscale is ba.UIScale.SMALL and ba.app.ui.use_toolbars: ba.widget( edit=btn1, - left_widget=ba.internal.get_special_widget('back_button')) + left_widget=ba.internal.get_special_widget('back_button'), + ) btnv -= b_height + b_space_extra - ba.buttonwidget(parent=cnt, - size=(b_width, b_height), - position=(btnh, btnv), - button_type='square', - color=b_color, - textcolor=b_textcolor, - on_activate_call=self._on_my_replay_rename_press, - text_scale=tscl, - label=ba.Lstr(resource=self._r + - '.renameReplayButtonText'), - autoselect=True) + ba.buttonwidget( + parent=cnt, + size=(b_width, b_height), + position=(btnh, btnv), + button_type='square', + color=b_color, + textcolor=b_textcolor, + on_activate_call=self._on_my_replay_rename_press, + text_scale=tscl, + label=ba.Lstr(resource=self._r + '.renameReplayButtonText'), + autoselect=True, + ) btnv -= b_height + b_space_extra - ba.buttonwidget(parent=cnt, - size=(b_width, b_height), - position=(btnh, btnv), - button_type='square', - color=b_color, - textcolor=b_textcolor, - on_activate_call=self._on_my_replay_delete_press, - text_scale=tscl, - label=ba.Lstr(resource=self._r + - '.deleteReplayButtonText'), - autoselect=True) + ba.buttonwidget( + parent=cnt, + size=(b_width, b_height), + position=(btnh, btnv), + button_type='square', + color=b_color, + textcolor=b_textcolor, + on_activate_call=self._on_my_replay_delete_press, + text_scale=tscl, + label=ba.Lstr(resource=self._r + '.deleteReplayButtonText'), + autoselect=True, + ) v -= sub_scroll_height + 23 self._scrollwidget = scrlw = ba.scrollwidget( parent=cnt, position=(smlh, v), - size=(sub_scroll_width, sub_scroll_height)) + size=(sub_scroll_width, sub_scroll_height), + ) ba.containerwidget(edit=cnt, selected_child=scrlw) - self._columnwidget = ba.columnwidget(parent=scrlw, - left_border=10, - border=2, - margin=0) + self._columnwidget = ba.columnwidget( + parent=scrlw, left_border=10, border=2, margin=0 + ) - ba.widget(edit=scrlw, - autoselect=True, - left_widget=btn1, - up_widget=self._tab_row.tabs[tab_id].button) - ba.widget(edit=self._tab_row.tabs[tab_id].button, - down_widget=scrlw) + ba.widget( + edit=scrlw, + autoselect=True, + left_widget=btn1, + up_widget=self._tab_row.tabs[tab_id].button, + ) + ba.widget(edit=self._tab_row.tabs[tab_id].button, down_widget=scrlw) self._my_replay_selected = None self._refresh_my_replays() def _no_replay_selected_error(self) -> None: - ba.screenmessage(ba.Lstr(resource=self._r + - '.noReplaySelectedErrorText'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr(resource=self._r + '.noReplaySelectedErrorText'), + color=(1, 0, 0), + ) ba.playsound(ba.getsound('error')) def _on_my_replay_play_press(self) -> None: @@ -292,14 +357,18 @@ class WatchWindow(ba.Window): ba.internal.set_replay_speed_exponent(0) ba.internal.fade_screen(True) assert self._my_replay_selected is not None - ba.internal.new_replay_session(ba.internal.get_replays_dir() + - '/' + self._my_replay_selected) + ba.internal.new_replay_session( + ba.internal.get_replays_dir() + + '/' + + self._my_replay_selected + ) except Exception: ba.print_exception('Error running replay session.') # Drop back into a fresh main menu session # in case we half-launched or something. from bastd import mainmenu + ba.internal.new_host_session(mainmenu.MainMenuSession) ba.internal.fade_screen(False, endcall=ba.Call(ba.pushcall, do_it)) @@ -313,19 +382,29 @@ class WatchWindow(ba.Window): c_height = 250 uiscale = ba.app.ui.uiscale self._my_replays_rename_window = cnt = ba.containerwidget( - scale=(1.8 if uiscale is ba.UIScale.SMALL else - 1.55 if uiscale is ba.UIScale.MEDIUM else 1.0), + scale=( + 1.8 + if uiscale is ba.UIScale.SMALL + else 1.55 + if uiscale is ba.UIScale.MEDIUM + else 1.0 + ), size=(c_width, c_height), - transition='in_scale') + transition='in_scale', + ) dname = self._get_replay_display_name(self._my_replay_selected) - ba.textwidget(parent=cnt, - size=(0, 0), - h_align='center', - v_align='center', - text=ba.Lstr(resource=self._r + '.renameReplayText', - subs=[('${REPLAY}', dname)]), - maxwidth=c_width * 0.8, - position=(c_width * 0.5, c_height - 60)) + ba.textwidget( + parent=cnt, + size=(0, 0), + h_align='center', + v_align='center', + text=ba.Lstr( + resource=self._r + '.renameReplayText', + subs=[('${REPLAY}', dname)], + ), + maxwidth=c_width * 0.8, + position=(c_width * 0.5, c_height - 60), + ) self._my_replay_rename_text = txt = ba.textwidget( parent=cnt, size=(c_width * 0.8, 40), @@ -337,24 +416,29 @@ class WatchWindow(ba.Window): position=(c_width * 0.1, c_height - 140), autoselect=True, maxwidth=c_width * 0.7, - max_chars=200) + max_chars=200, + ) cbtn = ba.buttonwidget( parent=cnt, label=ba.Lstr(resource='cancelText'), on_activate_call=ba.Call( lambda c: ba.containerwidget(edit=c, transition='out_scale'), - cnt), + cnt, + ), size=(180, 60), position=(30, 30), - autoselect=True) - okb = ba.buttonwidget(parent=cnt, - label=ba.Lstr(resource=self._r + '.renameText'), - size=(180, 60), - position=(c_width - 230, 30), - on_activate_call=ba.Call( - self._rename_my_replay, - self._my_replay_selected), - autoselect=True) + autoselect=True, + ) + okb = ba.buttonwidget( + parent=cnt, + label=ba.Lstr(resource=self._r + '.renameText'), + size=(180, 60), + position=(c_width - 230, 30), + on_activate_call=ba.Call( + self._rename_my_replay, self._my_replay_selected + ), + autoselect=True, + ) ba.widget(edit=cbtn, right_widget=okb) ba.widget(edit=okb, left_widget=cbtn) ba.textwidget(edit=txt, on_return_press_call=okb.activate) @@ -366,30 +450,41 @@ class WatchWindow(ba.Window): if not self._my_replay_rename_text: return new_name_raw = cast( - str, ba.textwidget(query=self._my_replay_rename_text)) + str, ba.textwidget(query=self._my_replay_rename_text) + ) new_name = new_name_raw + '.brp' # Ignore attempts to change it to what it already is # (or what it looks like to the user). - if (replay != new_name - and self._get_replay_display_name(replay) != new_name_raw): - old_name_full = (ba.internal.get_replays_dir() + '/' + - replay).encode('utf-8') - new_name_full = (ba.internal.get_replays_dir() + '/' + - new_name).encode('utf-8') + if ( + replay != new_name + and self._get_replay_display_name(replay) != new_name_raw + ): + old_name_full = ( + ba.internal.get_replays_dir() + '/' + replay + ).encode('utf-8') + new_name_full = ( + ba.internal.get_replays_dir() + '/' + new_name + ).encode('utf-8') # False alarm; ba.textwidget can return non-None val. # pylint: disable=unsupported-membership-test if os.path.exists(new_name_full): ba.playsound(ba.getsound('error')) ba.screenmessage( - ba.Lstr(resource=self._r + - '.replayRenameErrorAlreadyExistsText'), - color=(1, 0, 0)) + ba.Lstr( + resource=self._r + + '.replayRenameErrorAlreadyExistsText' + ), + color=(1, 0, 0), + ) elif any(char in new_name_raw for char in ['/', '\\', ':']): ba.playsound(ba.getsound('error')) - ba.screenmessage(ba.Lstr(resource=self._r + - '.replayRenameErrorInvalidName'), - color=(1, 0, 0)) + ba.screenmessage( + ba.Lstr( + resource=self._r + '.replayRenameErrorInvalidName' + ), + color=(1, 0, 0), + ) else: ba.internal.increment_analytics_count('Replay rename') os.rename(old_name_full, new_name_full) @@ -397,27 +492,38 @@ class WatchWindow(ba.Window): ba.playsound(ba.getsound('gunCocking')) except Exception: ba.print_exception( - f"Error renaming replay '{replay}' to '{new_name}'.") + f"Error renaming replay '{replay}' to '{new_name}'." + ) ba.playsound(ba.getsound('error')) ba.screenmessage( ba.Lstr(resource=self._r + '.replayRenameErrorText'), color=(1, 0, 0), ) - ba.containerwidget(edit=self._my_replays_rename_window, - transition='out_scale') + ba.containerwidget( + edit=self._my_replays_rename_window, transition='out_scale' + ) def _on_my_replay_delete_press(self) -> None: from bastd.ui import confirm + if self._my_replay_selected is None: self._no_replay_selected_error() return confirm.ConfirmWindow( - ba.Lstr(resource=self._r + '.deleteConfirmText', - subs=[('${REPLAY}', - self._get_replay_display_name( - self._my_replay_selected))]), - ba.Call(self._delete_replay, self._my_replay_selected), 450, 150) + ba.Lstr( + resource=self._r + '.deleteConfirmText', + subs=[ + ( + '${REPLAY}', + self._get_replay_display_name(self._my_replay_selected), + ) + ], + ), + ba.Call(self._delete_replay, self._my_replay_selected), + 450, + 150, + ) def _get_replay_display_name(self, replay: str) -> str: if replay.endswith('.brp'): @@ -430,7 +536,8 @@ class WatchWindow(ba.Window): try: ba.internal.increment_analytics_count('Replay delete') os.remove( - (ba.internal.get_replays_dir() + '/' + replay).encode('utf-8')) + (ba.internal.get_replays_dir() + '/' + replay).encode('utf-8') + ) self._refresh_my_replays() ba.playsound(ba.getsound('shieldDown')) if replay == self._my_replay_selected: @@ -468,8 +575,9 @@ class WatchWindow(ba.Window): parent=self._columnwidget, size=(self._my_replays_scroll_width / t_scale, 30), selectable=True, - color=(1.0, 1, 0.4) if name == '__lastReplay.brp' else - (1, 1, 1), + color=(1.0, 1, 0.4) + if name == '__lastReplay.brp' + else (1, 1, 1), always_highlight=True, on_select_call=ba.Call(self._on_my_replay_select, name), on_activate_call=self._my_replays_watch_replay_button.activate, @@ -477,17 +585,20 @@ class WatchWindow(ba.Window): h_align='left', v_align='center', corner_scale=t_scale, - maxwidth=(self._my_replays_scroll_width / t_scale) * 0.93) + maxwidth=(self._my_replays_scroll_width / t_scale) * 0.93, + ) if i == 0: ba.widget( edit=txt, - up_widget=self._tab_row.tabs[self.TabID.MY_REPLAYS].button) + up_widget=self._tab_row.tabs[self.TabID.MY_REPLAYS].button, + ) def _save_state(self) -> None: try: sel = self._root_widget.get_selected_child() selected_tab_ids = [ - tab_id for tab_id, tab in self._tab_row.tabs.items() + tab_id + for tab_id, tab in self._tab_row.tabs.items() if sel == tab.button ] if sel == self._back_button: @@ -505,14 +616,17 @@ class WatchWindow(ba.Window): def _restore_state(self) -> None: from efro.util import enum_by_value + try: sel: ba.Widget | None - sel_name = ba.app.ui.window_states.get(type(self), - {}).get('sel_name') + sel_name = ba.app.ui.window_states.get(type(self), {}).get( + 'sel_name' + ) assert isinstance(sel_name, (str, type(None))) try: - current_tab = enum_by_value(self.TabID, - ba.app.config.get('Watch Tab')) + current_tab = enum_by_value( + self.TabID, ba.app.config.get('Watch Tab') + ) except ValueError: current_tab = self.TabID.MY_REPLAYS self._set_tab(current_tab) @@ -523,8 +637,9 @@ class WatchWindow(ba.Window): sel = self._tab_container elif isinstance(sel_name, str) and sel_name.startswith('Tab:'): try: - sel_tab_id = enum_by_value(self.TabID, - sel_name.split(':')[-1]) + sel_tab_id = enum_by_value( + self.TabID, sel_name.split(':')[-1] + ) except ValueError: sel_tab_id = self.TabID.MY_REPLAYS sel = self._tab_row.tabs[sel_tab_id].button @@ -539,8 +654,11 @@ class WatchWindow(ba.Window): def _back(self) -> None: from bastd.ui.mainmenu import MainMenuWindow + self._save_state() - ba.containerwidget(edit=self._root_widget, - transition=self._transition_out) + ba.containerwidget( + edit=self._root_widget, transition=self._transition_out + ) ba.app.ui.set_main_menu_window( - MainMenuWindow(transition='in_left').get_root_widget()) + MainMenuWindow(transition='in_left').get_root_widget() + ) diff --git a/dist/ba_data/python/efro/call.py b/dist/ba_data/python/efro/call.py index 9087a0f..7013f3d 100644 --- a/dist/ba_data/python/efro/call.py +++ b/dist/ba_data/python/efro/call.py @@ -122,44 +122,67 @@ if TYPE_CHECKING: def __init__(self, _call: Callable[[In1T, In2T, In3T, In4T], OutT]): ... - def __call__(self, _arg1: In1T, _arg2: In2T, _arg3: In3T, - _arg4: 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 __init__( + self, _call: Callable[[In1T, In2T, In3T, In4T, In5T], OutT] + ): ... - def __call__(self, _arg1: In1T, _arg2: In2T, _arg3: In3T, _arg4: In4T, - _arg5: 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 __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: + 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]): + 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: + def __call__( + self, + _arg1: In1T, + _arg2: In2T, + _arg3: In3T, + _arg4: In4T, + _arg5: In5T, + _arg6: In6T, + _arg7: In7T, + ) -> OutT: ... # No arg call; no args bundled. @@ -183,44 +206,52 @@ if TYPE_CHECKING: # 2 arg call; 2 args bundled. # noinspection PyPep8Naming @overload - def Call(call: Callable[[In1T, In2T], OutT], arg1: In1T, - arg2: In2T) -> _CallNoArgs[OutT]: + 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]: + 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]: + 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]: + 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]: + 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]: + def Call( + call: Callable[[In1T, In2T, In3T], OutT], arg1: In1T + ) -> _Call2Args[In2T, In3T, OutT]: ... # 3 arg call; no args bundled. @@ -234,32 +265,55 @@ if TYPE_CHECKING: # 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]: + def Call( + call: Callable[[In1T, In2T, In3T, In4T], OutT], + arg1: In1T, + arg2: In2T, + arg3: In3T, + arg4: In4T, + ) -> _CallNoArgs[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]: + 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]: + 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]: + 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 diff --git a/dist/ba_data/python/efro/dataclassio/__init__.py b/dist/ba_data/python/efro/dataclassio/__init__.py index f88be16..2447ed8 100644 --- a/dist/ba_data/python/efro/dataclassio/__init__.py +++ b/dist/ba_data/python/efro/dataclassio/__init__.py @@ -11,19 +11,38 @@ data formats in a nondestructive manner. from __future__ import annotations from efro.util import set_canonical_module -from efro.dataclassio._base import (Codec, IOAttrs, IOExtendedData) -from efro.dataclassio._prep import (ioprep, ioprepped, will_ioprep, - is_ioprepped_dataclass) +from efro.dataclassio._base import Codec, IOAttrs, IOExtendedData +from efro.dataclassio._prep import ( + ioprep, + ioprepped, + will_ioprep, + is_ioprepped_dataclass, +) from efro.dataclassio._pathcapture import DataclassFieldLookup -from efro.dataclassio._api import (JsonStyle, dataclass_to_dict, - dataclass_to_json, dataclass_from_dict, - dataclass_from_json, dataclass_validate) +from efro.dataclassio._api import ( + JsonStyle, + dataclass_to_dict, + dataclass_to_json, + dataclass_from_dict, + dataclass_from_json, + dataclass_validate, +) __all__ = [ - 'JsonStyle', 'Codec', 'IOAttrs', 'IOExtendedData', 'ioprep', 'ioprepped', - 'will_ioprep', 'is_ioprepped_dataclass', 'DataclassFieldLookup', - 'dataclass_to_dict', 'dataclass_to_json', 'dataclass_from_dict', - 'dataclass_from_json', 'dataclass_validate' + 'JsonStyle', + 'Codec', + 'IOAttrs', + 'IOExtendedData', + 'ioprep', + 'ioprepped', + 'will_ioprep', + 'is_ioprepped_dataclass', + 'DataclassFieldLookup', + 'dataclass_to_dict', + 'dataclass_to_json', + 'dataclass_from_dict', + 'dataclass_from_json', + 'dataclass_validate', ] # Have these things present themselves cleanly as 'thismodule.SomeClass' diff --git a/dist/ba_data/python/efro/dataclassio/_api.py b/dist/ba_data/python/efro/dataclassio/_api.py index 13eb5f9..ddadd4d 100644 --- a/dist/ba_data/python/efro/dataclassio/_api.py +++ b/dist/ba_data/python/efro/dataclassio/_api.py @@ -39,9 +39,9 @@ class JsonStyle(Enum): PRETTY = 'pretty' -def dataclass_to_dict(obj: Any, - codec: Codec = Codec.JSON, - coerce_to_float: bool = True) -> dict: +def dataclass_to_dict( + obj: Any, codec: Codec = Codec.JSON, coerce_to_float: bool = True +) -> dict: """Given a dataclass object, return a json-friendly dict. All values will be checked to ensure they match the types specified @@ -58,18 +58,19 @@ def dataclass_to_dict(obj: Any, will be triggered. """ - out = _Outputter(obj, - create=True, - codec=codec, - coerce_to_float=coerce_to_float).run() + out = _Outputter( + obj, create=True, codec=codec, coerce_to_float=coerce_to_float + ).run() assert isinstance(out, dict) return out -def dataclass_to_json(obj: Any, - coerce_to_float: bool = True, - pretty: bool = False, - sort_keys: bool | None = None) -> str: +def dataclass_to_json( + obj: Any, + coerce_to_float: bool = True, + pretty: bool = False, + sort_keys: bool | None = None, +) -> str: """Utility function; return a json string from a dataclass instance. Basically json.dumps(dataclass_to_dict(...)). @@ -77,9 +78,10 @@ def dataclass_to_json(obj: Any, this can be overridden by supplying a value for the 'sort_keys' arg. """ import json - jdict = dataclass_to_dict(obj=obj, - coerce_to_float=coerce_to_float, - codec=Codec.JSON) + + jdict = dataclass_to_dict( + obj=obj, coerce_to_float=coerce_to_float, codec=Codec.JSON + ) if sort_keys is None: sort_keys = pretty if pretty: @@ -87,12 +89,14 @@ def dataclass_to_json(obj: Any, return json.dumps(jdict, separators=(',', ':'), sort_keys=sort_keys) -def dataclass_from_dict(cls: type[T], - values: dict, - codec: Codec = Codec.JSON, - coerce_to_float: bool = True, - allow_unknown_attrs: bool = True, - discard_unknown_attrs: bool = False) -> T: +def dataclass_from_dict( + cls: type[T], + values: dict, + codec: Codec = Codec.JSON, + coerce_to_float: bool = True, + allow_unknown_attrs: bool = True, + discard_unknown_attrs: bool = False, +) -> T: """Given a dict, return a dataclass of a given type. The dict must be formatted to match the specified codec (generally @@ -116,36 +120,44 @@ def dataclass_from_dict(cls: type[T], exported back to a dict, unless discard_unknown_attrs is True, in which case they will simply be discarded. """ - return _Inputter(cls, - codec=codec, - coerce_to_float=coerce_to_float, - allow_unknown_attrs=allow_unknown_attrs, - discard_unknown_attrs=discard_unknown_attrs).run(values) + return _Inputter( + cls, + codec=codec, + coerce_to_float=coerce_to_float, + allow_unknown_attrs=allow_unknown_attrs, + discard_unknown_attrs=discard_unknown_attrs, + ).run(values) -def dataclass_from_json(cls: type[T], - json_str: str, - coerce_to_float: bool = True, - allow_unknown_attrs: bool = True, - discard_unknown_attrs: bool = False) -> T: +def dataclass_from_json( + cls: type[T], + json_str: str, + coerce_to_float: bool = True, + allow_unknown_attrs: bool = True, + discard_unknown_attrs: bool = False, +) -> T: """Utility function; return a dataclass instance given a json string. Basically dataclass_from_dict(json.loads(...)) """ import json - return dataclass_from_dict(cls=cls, - values=json.loads(json_str), - coerce_to_float=coerce_to_float, - allow_unknown_attrs=allow_unknown_attrs, - discard_unknown_attrs=discard_unknown_attrs) + + return dataclass_from_dict( + cls=cls, + values=json.loads(json_str), + coerce_to_float=coerce_to_float, + allow_unknown_attrs=allow_unknown_attrs, + discard_unknown_attrs=discard_unknown_attrs, + ) -def dataclass_validate(obj: Any, - coerce_to_float: bool = True, - codec: Codec = Codec.JSON) -> None: +def dataclass_validate( + obj: Any, coerce_to_float: bool = True, codec: Codec = Codec.JSON +) -> 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).run() + _Outputter( + obj, create=False, codec=codec, coerce_to_float=coerce_to_float + ).run() diff --git a/dist/ba_data/python/efro/dataclassio/_base.py b/dist/ba_data/python/efro/dataclassio/_base.py index ee7834d..134ea00 100644 --- a/dist/ba_data/python/efro/dataclassio/_base.py +++ b/dist/ba_data/python/efro/dataclassio/_base.py @@ -9,6 +9,7 @@ import typing import datetime from enum import Enum from typing import TYPE_CHECKING, get_args + # noinspection PyProtectedMember from typing import _AnnotatedAlias # type: ignore @@ -23,8 +24,9 @@ SIMPLE_TYPES = {int, bool, str, float, type(None)} EXTRA_ATTRS_ATTR = '_DCIOEXATTRS' -def _raise_type_error(fieldpath: str, valuetype: type, - expected: tuple[type, ...]) -> None: +def _raise_type_error( + fieldpath: str, valuetype: type, expected: tuple[type, ...] +) -> None: """Raise an error when a field value's type does not match expected.""" assert isinstance(expected, tuple) assert all(isinstance(e, type) for e in expected) @@ -32,9 +34,11 @@ def _raise_type_error(fieldpath: str, valuetype: type, expected_str = expected[0].__name__ else: expected_str = ' | '.join(t.__name__ for t in expected) - raise TypeError(f'Invalid value type for "{fieldpath}";' - f' expected "{expected_str}", got' - f' "{valuetype.__name__}".') + raise TypeError( + f'Invalid value type for "{fieldpath}";' + f' expected "{expected_str}", got' + f' "{valuetype.__name__}".' + ) class Codec(Enum): @@ -83,7 +87,8 @@ def _is_valid_for_codec(obj: Any, codec: Codec) -> bool: # JSON 'objects' supports only string dict keys, but all value types. return all( isinstance(k, str) and _is_valid_for_codec(v, codec) - for k, v in obj.items()) + for k, v in obj.items() + ) if objtype is list: return all(_is_valid_for_codec(elem, codec) for elem in obj) @@ -162,13 +167,15 @@ class IOAttrs: if isinstance(soft_default, (list, dict, set)): raise ValueError( f'mutable {type(soft_default)} is not allowed' - f' for soft_default; use soft_default_factory.') + f' for soft_default; use soft_default_factory.' + ) self.soft_default = soft_default if soft_default_factory is not cls.soft_default_factory: self.soft_default_factory = soft_default_factory if self.soft_default is not cls.soft_default: - raise ValueError('Cannot set both soft_default' - ' and soft_default_factory') + raise ValueError( + 'Cannot set both soft_default and soft_default_factory' + ) def validate_for_field(self, cls: type, field: dataclasses.Field) -> None: """Ensure the IOAttrs instance is ok to use with the provided field.""" @@ -178,29 +185,44 @@ class IOAttrs: if not self.store_default: field_default_factory: Any = field.default_factory - if (field_default_factory is dataclasses.MISSING - and field.default is dataclasses.MISSING - and self.soft_default is self.MISSING - and self.soft_default_factory is self.MISSING): - raise TypeError(f'Field {field.name} of {cls} has' - f' neither a default nor a default_factory' - f' and IOAttrs contains neither a soft_default' - f' nor a soft_default_factory;' - f' store_default=False cannot be set for it.') + if ( + field_default_factory is dataclasses.MISSING + and field.default is dataclasses.MISSING + and self.soft_default is self.MISSING + and self.soft_default_factory is self.MISSING + ): + raise TypeError( + f'Field {field.name} of {cls} has' + f' neither a default nor a default_factory' + f' and IOAttrs contains neither a soft_default' + f' nor a soft_default_factory;' + f' store_default=False cannot be set for it.' + ) - def validate_datetime(self, value: datetime.datetime, - fieldpath: str) -> None: + def validate_datetime( + self, value: datetime.datetime, fieldpath: str + ) -> None: """Ensure a datetime value meets our value requirements.""" if self.whole_days: - if any(x != 0 for x in (value.hour, value.minute, value.second, - value.microsecond)): + if any( + x != 0 + for x in ( + value.hour, + value.minute, + value.second, + value.microsecond, + ) + ): raise ValueError( - f'Value {value} at {fieldpath} is not a whole day.') + f'Value {value} at {fieldpath} is not a whole day.' + ) if self.whole_hours: - if any(x != 0 - for x in (value.minute, value.second, value.microsecond)): - raise ValueError(f'Value {value} at {fieldpath}' - f' is not a whole hour.') + if any( + x != 0 for x in (value.minute, value.second, value.microsecond) + ): + raise ValueError( + f'Value {value} at {fieldpath}' f' is not a whole hour.' + ) def _get_origin(anntype: Any) -> Any: @@ -228,7 +250,8 @@ def _parse_annotated(anntype: Any) -> tuple[Any, IOAttrs | None]: if ioattrs is not None: raise RuntimeError( 'Multiple IOAttrs instances found for a' - ' single annotation; this is not supported.') + ' single annotation; this is not supported.' + ) ioattrs = annarg # I occasionally just throw a 'x' down when I mean IOAttrs('x'); @@ -236,6 +259,7 @@ def _parse_annotated(anntype: Any) -> tuple[Any, IOAttrs | None]: elif isinstance(annarg, (str, int, float, bool)): raise RuntimeError( f'Raw {type(annarg)} found in Annotated[] entry:' - f' {anntype}; this is probably not what you intended.') + f' {anntype}; this is probably not what you intended.' + ) anntype = annargs[0] return anntype, ioattrs diff --git a/dist/ba_data/python/efro/dataclassio/_inputter.py b/dist/ba_data/python/efro/dataclassio/_inputter.py index 85f4516..9442a25 100644 --- a/dist/ba_data/python/efro/dataclassio/_inputter.py +++ b/dist/ba_data/python/efro/dataclassio/_inputter.py @@ -16,10 +16,16 @@ import datetime from typing import TYPE_CHECKING, Generic, TypeVar from efro.util import enum_by_value, check_utc -from efro.dataclassio._base import (Codec, _parse_annotated, EXTRA_ATTRS_ATTR, - _is_valid_for_codec, _get_origin, - SIMPLE_TYPES, _raise_type_error, - IOExtendedData) +from efro.dataclassio._base import ( + Codec, + _parse_annotated, + EXTRA_ATTRS_ATTR, + _is_valid_for_codec, + _get_origin, + SIMPLE_TYPES, + _raise_type_error, + IOExtendedData, +) from efro.dataclassio._prep import PrepSession if TYPE_CHECKING: @@ -32,13 +38,14 @@ T = TypeVar('T') class _Inputter(Generic[T]): - - def __init__(self, - cls: type[T], - codec: Codec, - coerce_to_float: bool, - allow_unknown_attrs: bool = True, - discard_unknown_attrs: bool = False): + def __init__( + self, + cls: type[T], + codec: Codec, + coerce_to_float: bool, + allow_unknown_attrs: bool = True, + discard_unknown_attrs: bool = False, + ): self._cls = cls self._codec = codec self._coerce_to_float = coerce_to_float @@ -47,8 +54,10 @@ class _Inputter(Generic[T]): self._soft_default_validator: _Outputter | None = None if not allow_unknown_attrs and discard_unknown_attrs: - raise ValueError('discard_unknown_attrs cannot be True' - ' when allow_unknown_attrs is False.') + raise ValueError( + 'discard_unknown_attrs cannot be True' + ' when allow_unknown_attrs is False.' + ) def run(self, values: dict) -> T: """Do the thing.""" @@ -62,8 +71,14 @@ class _Inputter(Generic[T]): assert isinstance(out, self._cls) return out - def _value_from_input(self, cls: type, fieldpath: str, anntype: Any, - value: Any, ioattrs: IOAttrs | None) -> Any: + def _value_from_input( + self, + cls: type, + fieldpath: str, + anntype: Any, + value: Any, + ioattrs: IOAttrs | None, + ) -> Any: """Convert an assigned value to what a dataclass field expects.""" # pylint: disable=too-many-return-statements # pylint: disable=too-many-branches @@ -72,11 +87,13 @@ class _Inputter(Generic[T]): if origin is typing.Any: if not _is_valid_for_codec(value, self._codec): - raise TypeError(f'Invalid value type for \'{fieldpath}\';' - f' \'Any\' typed values must contain only' - f' types directly supported by the specified' - f' codec ({self._codec.name}); found' - f' \'{type(value).__name__}\' which is not.') + raise TypeError( + f'Invalid value type for \'{fieldpath}\';' + f' \'Any\' typed values must contain only' + f' types directly supported by the specified' + f' codec ({self._codec.name}); found' + f' \'{type(value).__name__}\' which is not.' + ) return value if origin is typing.Union or origin is types.UnionType: @@ -89,8 +106,9 @@ class _Inputter(Generic[T]): c for c in typing.get_args(anntype) if c is not type(None) ] # noqa (pycodestyle complains about *is* with type) assert len(childanntypes_l) == 1 - return self._value_from_input(cls, fieldpath, childanntypes_l[0], - value, ioattrs) + return self._value_from_input( + cls, fieldpath, childanntypes_l[0], value, ioattrs + ) # Everything below this point assumes the annotation type resolves # to a concrete type. (This should have been verified at prep time). @@ -99,23 +117,29 @@ class _Inputter(Generic[T]): if origin in SIMPLE_TYPES: if type(value) is not origin: # Special case: if they want to coerce ints to floats, do so. - if (self._coerce_to_float and origin is float - and type(value) is int): + if ( + self._coerce_to_float + and origin is float + and type(value) is int + ): return float(value) - _raise_type_error(fieldpath, type(value), (origin, )) + _raise_type_error(fieldpath, type(value), (origin,)) return value if origin in {list, set}: - return self._sequence_from_input(cls, fieldpath, anntype, value, - origin, ioattrs) + return self._sequence_from_input( + cls, fieldpath, anntype, value, origin, ioattrs + ) if origin is tuple: - return self._tuple_from_input(cls, fieldpath, anntype, value, - ioattrs) + return self._tuple_from_input( + cls, fieldpath, anntype, value, ioattrs + ) if origin is dict: - return self._dict_from_input(cls, fieldpath, anntype, value, - ioattrs) + return self._dict_from_input( + cls, fieldpath, anntype, value, ioattrs + ) if dataclasses.is_dataclass(origin): return self._dataclass_from_input(origin, fieldpath, value) @@ -130,10 +154,10 @@ class _Inputter(Generic[T]): return self._bytes_from_input(origin, fieldpath, value) raise TypeError( - f"Field '{fieldpath}' of type '{anntype}' is unsupported here.") + f"Field '{fieldpath}' of type '{anntype}' is unsupported here." + ) - def _bytes_from_input(self, cls: type, fieldpath: str, - value: Any) -> bytes: + def _bytes_from_input(self, cls: type, fieldpath: str, value: Any) -> bytes: """Given input data, returns bytes.""" import base64 @@ -141,19 +165,24 @@ class _Inputter(Generic[T]): # as base64. if self._codec is Codec.FIRESTORE: if not isinstance(value, bytes): - raise TypeError(f'Expected a bytes object for {fieldpath}' - f' on {cls.__name__}; got a {type(value)}.') + raise TypeError( + f'Expected a bytes object for {fieldpath}' + f' on {cls.__name__}; got a {type(value)}.' + ) return value assert self._codec is Codec.JSON if not isinstance(value, str): - raise TypeError(f'Expected a string object for {fieldpath}' - f' on {cls.__name__}; got a {type(value)}.') + raise TypeError( + f'Expected a string object for {fieldpath}' + f' on {cls.__name__}; got a {type(value)}.' + ) return base64.b64decode(value) - def _dataclass_from_input(self, cls: type, fieldpath: str, - values: dict) -> Any: + def _dataclass_from_input( + self, cls: type, fieldpath: str, values: dict + ) -> Any: """Given a dict, instantiates a dataclass of the given type. The dict must be in the json-friendly format as emitted from @@ -166,10 +195,12 @@ class _Inputter(Generic[T]): if not isinstance(values, dict): raise TypeError( f'Expected a dict for {fieldpath} on {cls.__name__};' - f' got a {type(values)}.') + f' got a {type(values)}.' + ) - prep = PrepSession(explicit=False).prep_dataclass(cls, - recursion_level=0) + prep = PrepSession(explicit=False).prep_dataclass( + cls, recursion_level=0 + ) assert prep is not None extra_attrs = {} @@ -181,8 +212,7 @@ class _Inputter(Generic[T]): # Preprocess all fields to convert Annotated[] to contained types # and IOAttrs. parsed_field_annotations = { - f.name: _parse_annotated(prep.annotations[f.name]) - for f in fields + f.name: _parse_annotated(prep.annotations[f.name]) for f in fields } # Go through all data in the input, converting it to either dataclass @@ -205,18 +235,22 @@ class _Inputter(Generic[T]): f'Unknown attr \'{key}\'' f' on {fieldpath} contains data type(s)' f' not supported by the specified codec' - f' ({self._codec.name}).') + f' ({self._codec.name}).' + ) extra_attrs[key] = value else: raise AttributeError( - f"'{cls.__name__}' has no '{key}' field.") + f"'{cls.__name__}' has no '{key}' field." + ) else: fieldname = field.name anntype, ioattrs = parsed_field_annotations[fieldname] - subfieldpath = (f'{fieldpath}.{fieldname}' - if fieldpath else fieldname) - args[key] = self._value_from_input(cls, subfieldpath, anntype, - value, ioattrs) + subfieldpath = ( + f'{fieldpath}.{fieldname}' if fieldpath else fieldname + ) + args[key] = self._value_from_input( + cls, subfieldpath, anntype, value, ioattrs + ) # Go through all fields looking for any not yet present in our data. # If we find any such fields with a soft-default value or factory @@ -225,9 +259,10 @@ class _Inputter(Generic[T]): if key in args: continue ioattrs = aparsed[1] - if (ioattrs is not None and - (ioattrs.soft_default is not ioattrs.MISSING - or ioattrs.soft_default_factory is not ioattrs.MISSING)): + if ioattrs is not None and ( + ioattrs.soft_default is not ioattrs.MISSING + or ioattrs.soft_default_factory is not ioattrs.MISSING + ): if ioattrs.soft_default is not ioattrs.MISSING: soft_default = ioattrs.soft_default else: @@ -241,19 +276,23 @@ class _Inputter(Generic[T]): self._type_check_soft_default( value=soft_default, anntype=aparsed[0], - fieldpath=(f'{fieldpath}.{key}' if fieldpath else key)) + fieldpath=(f'{fieldpath}.{key}' if fieldpath else key), + ) try: out = cls(**args) except Exception as exc: - raise ValueError(f'Error instantiating class {cls.__name__}' - f' at {fieldpath}: {exc}') from exc + raise ValueError( + f'Error instantiating class {cls.__name__}' + f' at {fieldpath}: {exc}' + ) from exc if extra_attrs: setattr(out, EXTRA_ATTRS_ATTR, extra_attrs) return out - def _type_check_soft_default(self, value: Any, anntype: Any, - fieldpath: str) -> None: + def _type_check_soft_default( + self, value: Any, anntype: Any, fieldpath: str + ) -> None: from efro.dataclassio._outputter import _Outputter # Counter-intuitively, we create an outputter as part of @@ -264,20 +303,28 @@ class _Inputter(Generic[T]): obj=None, create=False, codec=self._codec, - coerce_to_float=self._coerce_to_float) - self._soft_default_validator.soft_default_check(value=value, - anntype=anntype, - fieldpath=fieldpath) + coerce_to_float=self._coerce_to_float, + ) + self._soft_default_validator.soft_default_check( + value=value, anntype=anntype, fieldpath=fieldpath + ) - def _dict_from_input(self, cls: type, fieldpath: str, anntype: Any, - value: Any, ioattrs: IOAttrs | None) -> Any: + def _dict_from_input( + self, + cls: type, + fieldpath: str, + anntype: Any, + value: Any, + ioattrs: IOAttrs | None, + ) -> Any: # pylint: disable=too-many-branches # pylint: disable=too-many-locals if not isinstance(value, dict): raise TypeError( f'Expected a dict for \'{fieldpath}\' on {cls.__name__};' - f' got a {type(value)}.') + f' got a {type(value)}.' + ) childtypes = typing.get_args(anntype) assert len(childtypes) in (0, 2) @@ -287,12 +334,15 @@ class _Inputter(Generic[T]): # We treat 'Any' dicts simply as json; we don't do any translating. if not childtypes or childtypes[0] is typing.Any: if not isinstance(value, dict) or not _is_valid_for_codec( - value, self._codec): - raise TypeError(f'Got invalid value for Dict[Any, Any]' - f' at \'{fieldpath}\' on {cls.__name__};' - f' all keys and values must be' - f' compatible with the specified codec' - f' ({self._codec.name}).') + value, self._codec + ): + raise TypeError( + f'Got invalid value for Dict[Any, Any]' + f' at \'{fieldpath}\' on {cls.__name__};' + f' all keys and values must be' + f' compatible with the specified codec' + f' ({self._codec.name}).' + ) out = value else: out = {} @@ -308,9 +358,11 @@ class _Inputter(Generic[T]): raise TypeError( f'Got invalid key type {type(key)} for' f' dict key at \'{fieldpath}\' on {cls.__name__};' - f' expected a str.') - out[key] = self._value_from_input(cls, fieldpath, - valanntype, val, ioattrs) + f' expected a str.' + ) + out[key] = self._value_from_input( + cls, fieldpath, valanntype, val, ioattrs + ) # int keys are stored in json as str versions of themselves. elif keyanntype is int: @@ -319,16 +371,19 @@ class _Inputter(Generic[T]): raise TypeError( f'Got invalid key type {type(key)} for' f' dict key at \'{fieldpath}\' on {cls.__name__};' - f' expected a str.') + f' expected a str.' + ) try: keyint = int(key) except ValueError as exc: raise TypeError( f'Got invalid key value {key} for' f' dict key at \'{fieldpath}\' on {cls.__name__};' - f' expected an int in string form.') from exc + f' expected an int in string form.' + ) from exc out[keyint] = self._value_from_input( - cls, fieldpath, valanntype, val, ioattrs) + cls, fieldpath, valanntype, val, ioattrs + ) elif issubclass(keyanntype, Enum): # In prep, we verified that all these enums' values have @@ -346,9 +401,11 @@ class _Inputter(Generic[T]): f' dict key at \'{fieldpath}\'' f' on {cls.__name__};' f' expected a value corresponding to' - f' a {keyanntype}.') from exc + f' a {keyanntype}.' + ) from exc out[enumval] = self._value_from_input( - cls, fieldpath, valanntype, val, ioattrs) + cls, fieldpath, valanntype, val, ioattrs + ) else: for key, val in value.items(): try: @@ -359,23 +416,33 @@ class _Inputter(Generic[T]): f' dict key at \'{fieldpath}\'' f' on {cls.__name__};' f' expected {keyanntype} value (though' - f' in string form).') from exc + f' in string form).' + ) from exc out[enumval] = self._value_from_input( - cls, fieldpath, valanntype, val, ioattrs) + cls, fieldpath, valanntype, val, ioattrs + ) else: raise RuntimeError(f'Unhandled dict in-key-type {keyanntype}') return out - def _sequence_from_input(self, cls: type, fieldpath: str, anntype: Any, - value: Any, seqtype: type, - ioattrs: IOAttrs | None) -> Any: + def _sequence_from_input( + self, + cls: type, + fieldpath: str, + anntype: Any, + value: Any, + seqtype: type, + ioattrs: IOAttrs | None, + ) -> Any: # Because we are json-centric, we expect a list for all sequences. if type(value) is not list: - raise TypeError(f'Invalid input value for "{fieldpath}";' - f' expected a list, got a {type(value).__name__}') + raise TypeError( + f'Invalid input value for "{fieldpath}";' + f' expected a list, got a {type(value).__name__}' + ) childanntypes = typing.get_args(anntype) @@ -384,8 +451,10 @@ class _Inputter(Generic[T]): if len(childanntypes) == 0 or childanntypes[0] is typing.Any: for i, child in enumerate(value): if not _is_valid_for_codec(child, self._codec): - raise TypeError(f'Item {i} of {fieldpath} contains' - f' data type(s) not supported by json.') + raise TypeError( + f'Item {i} of {fieldpath} contains' + f' data type(s) not supported by json.' + ) return value if type(value) is seqtype else seqtype(value) # We contain elements of some specified type. @@ -393,10 +462,12 @@ class _Inputter(Generic[T]): childanntype = childanntypes[0] return seqtype( self._value_from_input(cls, fieldpath, childanntype, i, ioattrs) - for i in value) + for i in value + ) - def _datetime_from_input(self, cls: type, fieldpath: str, value: Any, - ioattrs: IOAttrs | None) -> Any: + def _datetime_from_input( + self, cls: type, fieldpath: str, value: Any, ioattrs: IOAttrs | None + ) -> Any: # For firestore we expect a datetime object. if self._codec is Codec.FIRESTORE: @@ -406,7 +477,8 @@ class _Inputter(Generic[T]): raise TypeError( f'Invalid input value for "{fieldpath}" on' f' "{cls.__name__}";' - f' expected a datetime, got a {type(value).__name__}') + f' expected a datetime, got a {type(value).__name__}' + ) check_utc(value) return value @@ -416,26 +488,37 @@ class _Inputter(Generic[T]): if type(value) is not list: raise TypeError( f'Invalid input value for "{fieldpath}" on "{cls.__name__}";' - f' expected a list, got a {type(value).__name__}') + f' expected a list, got a {type(value).__name__}' + ) if len(value) != 7 or not all(isinstance(x, int) for x in value): raise ValueError( f'Invalid input value for "{fieldpath}" on "{cls.__name__}";' - f' expected a list of 7 ints, got {[type(v) for v in value]}.') + f' expected a list of 7 ints, got {[type(v) for v in value]}.' + ) out = datetime.datetime( # type: ignore - *value, tzinfo=datetime.timezone.utc) + *value, tzinfo=datetime.timezone.utc + ) if ioattrs is not None: ioattrs.validate_datetime(out, fieldpath) return out - def _tuple_from_input(self, cls: type, fieldpath: str, anntype: Any, - value: Any, ioattrs: IOAttrs | None) -> Any: + def _tuple_from_input( + self, + cls: type, + fieldpath: str, + anntype: Any, + value: Any, + ioattrs: IOAttrs | None, + ) -> Any: out: list = [] # Because we are json-centric, we expect a list for all sequences. if type(value) is not list: - raise TypeError(f'Invalid input value for "{fieldpath}";' - f' expected a list, got a {type(value).__name__}') + raise TypeError( + f'Invalid input value for "{fieldpath}";' + f' expected a list, got a {type(value).__name__}' + ) childanntypes = typing.get_args(anntype) @@ -443,9 +526,11 @@ class _Inputter(Generic[T]): assert childanntypes if len(value) != len(childanntypes): - raise ValueError(f'Invalid tuple input for "{fieldpath}";' - f' expected {len(childanntypes)} values,' - f' found {len(value)}.') + raise ValueError( + f'Invalid tuple input for "{fieldpath}";' + f' expected {len(childanntypes)} values,' + f' found {len(value)}.' + ) for i, childanntype in enumerate(childanntypes): childval = value[i] @@ -454,13 +539,17 @@ class _Inputter(Generic[T]): # and then just grab them. if childanntype is typing.Any: if not _is_valid_for_codec(childval, self._codec): - raise TypeError(f'Item {i} of {fieldpath} contains' - f' data type(s) not supported by json.') + raise TypeError( + f'Item {i} of {fieldpath} contains' + f' data type(s) not supported by json.' + ) out.append(childval) else: out.append( - self._value_from_input(cls, fieldpath, childanntype, - childval, ioattrs)) + self._value_from_input( + cls, fieldpath, childanntype, childval, ioattrs + ) + ) assert len(out) == len(childanntypes) return tuple(out) diff --git a/dist/ba_data/python/efro/dataclassio/_outputter.py b/dist/ba_data/python/efro/dataclassio/_outputter.py index 03965e1..65c5f39 100644 --- a/dist/ba_data/python/efro/dataclassio/_outputter.py +++ b/dist/ba_data/python/efro/dataclassio/_outputter.py @@ -16,10 +16,16 @@ import datetime from typing import TYPE_CHECKING from efro.util import check_utc -from efro.dataclassio._base import (Codec, _parse_annotated, EXTRA_ATTRS_ATTR, - _is_valid_for_codec, _get_origin, - SIMPLE_TYPES, _raise_type_error, - IOExtendedData) +from efro.dataclassio._base import ( + Codec, + _parse_annotated, + EXTRA_ATTRS_ATTR, + _is_valid_for_codec, + _get_origin, + SIMPLE_TYPES, + _raise_type_error, + IOExtendedData, +) from efro.dataclassio._prep import PrepSession if TYPE_CHECKING: @@ -30,8 +36,9 @@ if TYPE_CHECKING: class _Outputter: """Validates or exports data contained in a dataclass instance.""" - def __init__(self, obj: Any, create: bool, codec: Codec, - coerce_to_float: bool) -> None: + def __init__( + self, obj: Any, create: bool, codec: Codec, coerce_to_float: bool + ) -> None: self._obj = obj self._create = create self._codec = codec @@ -48,20 +55,24 @@ class _Outputter: return self._process_dataclass(type(self._obj), self._obj, '') - def soft_default_check(self, value: Any, anntype: Any, - fieldpath: str) -> None: + def soft_default_check( + self, value: Any, anntype: Any, fieldpath: str + ) -> None: """(internal)""" - self._process_value(type(value), - fieldpath=fieldpath, - anntype=anntype, - value=value, - ioattrs=None) + self._process_value( + type(value), + fieldpath=fieldpath, + anntype=anntype, + value=value, + ioattrs=None, + ) def _process_dataclass(self, cls: type, obj: Any, fieldpath: str) -> Any: # pylint: disable=too-many-locals # pylint: disable=too-many-branches - prep = PrepSession(explicit=False).prep_dataclass(type(obj), - recursion_level=0) + prep = PrepSession(explicit=False).prep_dataclass( + type(obj), recursion_level=0 + ) assert prep is not None fields = dataclasses.fields(obj) out: dict[str, Any] | None = {} if self._create else None @@ -102,15 +113,19 @@ class _Outputter: f'Field {fieldname} of {cls.__name__} has' f' no source of default values; store_default=False' f' cannot be set for it. (AND THIS SHOULD HAVE BEEN' - f' CAUGHT IN PREP!)') + f' CAUGHT IN PREP!)' + ) - outvalue = self._process_value(cls, subfieldpath, anntype, value, - ioattrs) + outvalue = self._process_value( + cls, subfieldpath, anntype, value, ioattrs + ) if self._create: assert out is not None - storagename = (fieldname if - (ioattrs is None or ioattrs.storagename is None) - else ioattrs.storagename) + storagename = ( + fieldname + if (ioattrs is None or ioattrs.storagename is None) + else ioattrs.storagename + ) out[storagename] = outvalue # If there's extra-attrs stored on us, check/include them. @@ -120,14 +135,21 @@ class _Outputter: raise TypeError( f'Extra attrs on \'{fieldpath}\' contains data type(s)' f' not supported by \'{self._codec.value}\' codec:' - f' {extra_attrs}.') + f' {extra_attrs}.' + ) if self._create: assert out is not None out.update(extra_attrs) return out - def _process_value(self, cls: type, fieldpath: str, anntype: Any, - value: Any, ioattrs: IOAttrs | None) -> Any: + def _process_value( + self, + cls: type, + fieldpath: str, + anntype: Any, + value: Any, + ioattrs: IOAttrs | None, + ) -> Any: # pylint: disable=too-many-return-statements # pylint: disable=too-many-branches # pylint: disable=too-many-statements @@ -140,7 +162,8 @@ class _Outputter: f'Invalid value type for \'{fieldpath}\';' f" 'Any' typed values must contain types directly" f' supported by the specified codec ({self._codec.name});' - f' found \'{type(value).__name__}\' which is not.') + f' found \'{type(value).__name__}\' which is not.' + ) return value if self._create else None if origin is typing.Union or origin is types.UnionType: @@ -153,8 +176,9 @@ class _Outputter: c for c in typing.get_args(anntype) if c is not type(None) ] # noqa (pycodestyle complains about *is* with type) assert len(childanntypes_l) == 1 - return self._process_value(cls, fieldpath, childanntypes_l[0], - value, ioattrs) + return self._process_value( + cls, fieldpath, childanntypes_l[0], value, ioattrs + ) # Everything below this point assumes the annotation type resolves # to a concrete type. (This should have been verified at prep time). @@ -164,38 +188,50 @@ class _Outputter: if origin in SIMPLE_TYPES: if type(value) is not origin: # Special case: if they want to coerce ints to floats, do so. - if (self._coerce_to_float and origin is float - and type(value) is int): + if ( + self._coerce_to_float + and origin is float + and type(value) is int + ): return float(value) if self._create else None - _raise_type_error(fieldpath, type(value), (origin, )) + _raise_type_error(fieldpath, type(value), (origin,)) return value if self._create else None if origin is tuple: if not isinstance(value, tuple): - raise TypeError(f'Expected a tuple for {fieldpath};' - f' found a {type(value)}') + raise TypeError( + f'Expected a tuple for {fieldpath};' + f' found a {type(value)}' + ) childanntypes = typing.get_args(anntype) # We should have verified this was non-zero at prep-time assert childanntypes if len(value) != len(childanntypes): - raise TypeError(f'Tuple at {fieldpath} contains' - f' {len(value)} values; type specifies' - f' {len(childanntypes)}.') + raise TypeError( + f'Tuple at {fieldpath} contains' + f' {len(value)} values; type specifies' + f' {len(childanntypes)}.' + ) if self._create: return [ - self._process_value(cls, fieldpath, childanntypes[i], x, - ioattrs) for i, x in enumerate(value) + self._process_value( + cls, fieldpath, childanntypes[i], x, ioattrs + ) + for i, x in enumerate(value) ] for i, x in enumerate(value): - self._process_value(cls, fieldpath, childanntypes[i], x, - ioattrs) + self._process_value( + cls, fieldpath, childanntypes[i], x, ioattrs + ) return None if origin is list: if not isinstance(value, list): - raise TypeError(f'Expected a list for {fieldpath};' - f' found a {type(value)}') + raise TypeError( + f'Expected a list for {fieldpath};' + f' found a {type(value)}' + ) childanntypes = typing.get_args(anntype) # 'Any' type children; make sure they are valid values for @@ -206,7 +242,8 @@ class _Outputter: raise TypeError( f'Item {i} of {fieldpath} contains' f' data type(s) not supported by the specified' - f' codec ({self._codec.name}).') + f' codec ({self._codec.name}).' + ) # Hmm; should we do a copy here? return value if self._create else None @@ -214,18 +251,22 @@ class _Outputter: assert len(childanntypes) == 1 if self._create: return [ - self._process_value(cls, fieldpath, childanntypes[0], x, - ioattrs) for x in value + self._process_value( + cls, fieldpath, childanntypes[0], x, ioattrs + ) + for x in value ] for x in value: - self._process_value(cls, fieldpath, childanntypes[0], x, - ioattrs) + self._process_value( + cls, fieldpath, childanntypes[0], x, ioattrs + ) return None if origin is set: if not isinstance(value, set): - raise TypeError(f'Expected a set for {fieldpath};' - f' found a {type(value)}') + raise TypeError( + f'Expected a set for {fieldpath};' f' found a {type(value)}' + ) childanntypes = typing.get_args(anntype) # 'Any' type children; make sure they are valid Any values. @@ -235,7 +276,8 @@ class _Outputter: raise TypeError( f'Set at {fieldpath} contains' f' data type(s) not supported by the' - f' specified codec ({self._codec.name}).') + f' specified codec ({self._codec.name}).' + ) return list(value) if self._create else None # We contain elements of some specified type. @@ -244,12 +286,15 @@ class _Outputter: # Note: we output json-friendly values so this becomes # a list. return [ - self._process_value(cls, fieldpath, childanntypes[0], x, - ioattrs) for x in value + self._process_value( + cls, fieldpath, childanntypes[0], x, ioattrs + ) + for x in value ] for x in value: - self._process_value(cls, fieldpath, childanntypes[0], x, - ioattrs) + self._process_value( + cls, fieldpath, childanntypes[0], x, ioattrs + ) return None if origin is dict: @@ -257,45 +302,63 @@ class _Outputter: if dataclasses.is_dataclass(origin): if not isinstance(value, origin): - raise TypeError(f'Expected a {origin} for {fieldpath};' - f' found a {type(value)}.') + raise TypeError( + f'Expected a {origin} for {fieldpath};' + f' found a {type(value)}.' + ) return self._process_dataclass(cls, value, fieldpath) if issubclass(origin, Enum): if not isinstance(value, origin): - raise TypeError(f'Expected a {origin} for {fieldpath};' - f' found a {type(value)}.') + raise TypeError( + f'Expected a {origin} for {fieldpath};' + f' found a {type(value)}.' + ) # At prep-time we verified that these enums had valid value # types, so we can blindly return it here. return value.value if self._create else None if issubclass(origin, datetime.datetime): if not isinstance(value, origin): - raise TypeError(f'Expected a {origin} for {fieldpath};' - f' found a {type(value)}.') + raise TypeError( + f'Expected a {origin} for {fieldpath};' + f' found a {type(value)}.' + ) check_utc(value) if ioattrs is not None: ioattrs.validate_datetime(value, fieldpath) if self._codec is Codec.FIRESTORE: return value assert self._codec is Codec.JSON - return [ - value.year, value.month, value.day, value.hour, value.minute, - value.second, value.microsecond - ] if self._create else None + return ( + [ + value.year, + value.month, + value.day, + value.hour, + value.minute, + value.second, + value.microsecond, + ] + if self._create + else None + ) if origin is bytes: return self._process_bytes(cls, fieldpath, value) raise TypeError( - f"Field '{fieldpath}' of type '{anntype}' is unsupported here.") + f"Field '{fieldpath}' of type '{anntype}' is unsupported here." + ) def _process_bytes(self, cls: type, fieldpath: str, value: bytes) -> Any: import base64 + if not isinstance(value, bytes): raise TypeError( f'Expected bytes for {fieldpath} on {cls.__name__};' - f' found a {type(value)}.') + f' found a {type(value)}.' + ) if not self._create: return None @@ -307,25 +370,34 @@ class _Outputter: assert self._codec is Codec.FIRESTORE return value - def _process_dict(self, cls: type, fieldpath: str, anntype: Any, - value: dict, ioattrs: IOAttrs | None) -> Any: + def _process_dict( + self, + cls: type, + fieldpath: str, + anntype: Any, + value: dict, + ioattrs: IOAttrs | None, + ) -> Any: # pylint: disable=too-many-branches if not isinstance(value, dict): - raise TypeError(f'Expected a dict for {fieldpath};' - f' found a {type(value)}.') + raise TypeError( + f'Expected a dict for {fieldpath};' f' found a {type(value)}.' + ) childtypes = typing.get_args(anntype) assert len(childtypes) in (0, 2) # We treat 'Any' dicts simply as json; we don't do any translating. if not childtypes or childtypes[0] is typing.Any: if not isinstance(value, dict) or not _is_valid_for_codec( - value, self._codec): + value, self._codec + ): raise TypeError( f'Invalid value for Dict[Any, Any]' f' at \'{fieldpath}\' on {cls.__name__};' f' all keys and values must be directly compatible' f' with the specified codec ({self._codec.name})' - f' when dict type is Any.') + f' when dict type is Any.' + ) return value if self._create else None # Ok; we've got a definite key type (which we verified as valid @@ -340,9 +412,11 @@ class _Outputter: raise TypeError( f'Got invalid key type {type(key)} for' f' dict key at \'{fieldpath}\' on {cls.__name__};' - f' expected {keyanntype}.') - outval = self._process_value(cls, fieldpath, valanntype, val, - ioattrs) + f' expected {keyanntype}.' + ) + outval = self._process_value( + cls, fieldpath, valanntype, val, ioattrs + ) if self._create: assert out is not None out[key] = outval @@ -354,9 +428,11 @@ class _Outputter: raise TypeError( f'Got invalid key type {type(key)} for' f' dict key at \'{fieldpath}\' on {cls.__name__};' - f' expected an int.') - outval = self._process_value(cls, fieldpath, valanntype, val, - ioattrs) + f' expected an int.' + ) + outval = self._process_value( + cls, fieldpath, valanntype, val, ioattrs + ) if self._create: assert out is not None out[str(key)] = outval @@ -367,9 +443,11 @@ class _Outputter: raise TypeError( f'Got invalid key type {type(key)} for' f' dict key at \'{fieldpath}\' on {cls.__name__};' - f' expected a {keyanntype}.') - outval = self._process_value(cls, fieldpath, valanntype, val, - ioattrs) + f' expected a {keyanntype}.' + ) + outval = self._process_value( + cls, fieldpath, valanntype, val, ioattrs + ) if self._create: assert out is not None out[str(key.value)] = outval diff --git a/dist/ba_data/python/efro/dataclassio/_pathcapture.py b/dist/ba_data/python/efro/dataclassio/_pathcapture.py index b0b34ad..2fb0c8a 100644 --- a/dist/ba_data/python/efro/dataclassio/_pathcapture.py +++ b/dist/ba_data/python/efro/dataclassio/_pathcapture.py @@ -32,18 +32,23 @@ class _PathCapture: if not self._is_dataclass: raise TypeError( f"Field path cannot include attribute '{name}' " - f'under parent {self._cls}; parent types must be dataclasses.') + f'under parent {self._cls}; parent types must be dataclasses.' + ) - prep = PrepSession(explicit=False).prep_dataclass(self._cls, - recursion_level=0) + prep = PrepSession(explicit=False).prep_dataclass( + self._cls, recursion_level=0 + ) assert prep is not None try: anntype = prep.annotations[name] except KeyError as exc: raise AttributeError(f'{type(self)} has no {name} field.') from exc anntype, ioattrs = _parse_annotated(anntype) - storagename = (name if (ioattrs is None or ioattrs.storagename is None) - else ioattrs.storagename) + storagename = ( + name + if (ioattrs is None or ioattrs.storagename is None) + else ioattrs.storagename + ) origin = _get_origin(anntype) return _PathCapture(origin, pathparts=self._pathparts + [storagename]) @@ -81,8 +86,10 @@ class DataclassFieldLookup(Generic[T]): if not TYPE_CHECKING: out = callback(_PathCapture(self.cls)) if not isinstance(out, _PathCapture): - raise TypeError(f'Expected a valid path under' - f' the provided object; got a {type(out)}.') + raise TypeError( + f'Expected a valid path under' + f' the provided object; got a {type(out)}.' + ) return out.path return '' @@ -102,6 +109,7 @@ class DataclassFieldLookup(Generic[T]): if not isinstance(out, _PathCapture): raise TypeError( f'Expected a valid path under' - f' the provided object; got a {type(out)}.') + f' the provided object; got a {type(out)}.' + ) outvals.append(out.path) return outvals diff --git a/dist/ba_data/python/efro/dataclassio/_prep.py b/dist/ba_data/python/efro/dataclassio/_prep.py index d49a132..d01db20 100644 --- a/dist/ba_data/python/efro/dataclassio/_prep.py +++ b/dist/ba_data/python/efro/dataclassio/_prep.py @@ -17,8 +17,7 @@ import datetime from typing import TYPE_CHECKING, TypeVar, get_type_hints # noinspection PyProtectedMember -from efro.dataclassio._base import (_parse_annotated, _get_origin, - SIMPLE_TYPES) +from efro.dataclassio._base import _parse_annotated, _get_origin, SIMPLE_TYPES if TYPE_CHECKING: from typing import Any @@ -58,8 +57,9 @@ def ioprep(cls: type, globalns: dict | None = None) -> None: prepping happens as part of an execed string instead of within a module. """ - PrepSession(explicit=True, - globalns=globalns).prep_dataclass(cls, recursion_level=0) + PrepSession(explicit=True, globalns=globalns).prep_dataclass( + cls, recursion_level=0 + ) def ioprepped(cls: type[T]) -> type[T]: @@ -119,8 +119,9 @@ class PrepSession: self.explicit = explicit self.globalns = globalns - def prep_dataclass(self, cls: type, - recursion_level: int) -> PrepData | None: + def prep_dataclass( + self, cls: type, recursion_level: int + ) -> PrepData | None: """Run prep on a dataclass if necessary and return its prep data. The only case where this will return None is for recursive types @@ -165,22 +166,27 @@ class PrepSession: ' It is highly recommended to explicitly prep dataclasses' ' as soon as possible after definition (via' ' efro.dataclassio.ioprep() or the' - ' @efro.dataclassio.ioprepped decorator).', cls) + ' @efro.dataclassio.ioprepped decorator).', + cls, + ) try: # NOTE: Now passing the class' __dict__ (vars()) as locals # which allows us to pick up nested classes, etc. - resolved_annotations = get_type_hints(cls, - localns=vars(cls), - globalns=self.globalns, - include_extras=True) + resolved_annotations = get_type_hints( + cls, + localns=vars(cls), + globalns=self.globalns, + include_extras=True, + ) # pylint: enable=unexpected-keyword-arg except Exception as exc: raise TypeError( f'dataclassio prep for {cls} failed with error: {exc}.' f' Make sure all types used in annotations are defined' f' at the module or class level or add them as part of an' - f' explicit prep call.') from exc + f' explicit prep call.' + ) from exc # noinspection PyDataclass fields = dataclasses.fields(cls) @@ -210,20 +216,25 @@ class PrepSession: # Make sure we don't have any clashes in our storage names. if storagename in all_storage_names: - raise TypeError(f'Multiple attrs on {cls} are using' - f' storage-name \'{storagename}\'') + raise TypeError( + f'Multiple attrs on {cls} are using' + f' storage-name \'{storagename}\'' + ) all_storage_names.add(storagename) - self.prep_type(cls, - attrname, - anntype, - ioattrs=ioattrs, - recursion_level=recursion_level + 1) + self.prep_type( + cls, + attrname, + anntype, + ioattrs=ioattrs, + recursion_level=recursion_level + 1, + ) # Success! Store our resolved stuff with the class and we're done. prepdata = PrepData( annotations=resolved_annotations, - storage_names_to_attr_names=storage_names_to_attr_names) + storage_names_to_attr_names=storage_names_to_attr_names, + ) setattr(cls, PREP_ATTR, prepdata) # Clear our prep-session tag. @@ -231,8 +242,14 @@ class PrepSession: delattr(cls, PREP_SESSION_ATTR) return prepdata - def prep_type(self, cls: type, attrname: str, anntype: Any, - ioattrs: IOAttrs | None, recursion_level: int) -> None: + def prep_type( + self, + cls: type, + attrname: str, + anntype: Any, + ioattrs: IOAttrs | None, + recursion_level: int, + ) -> None: """Run prep on a dataclass.""" # pylint: disable=too-many-return-statements # pylint: disable=too-many-branches @@ -244,10 +261,9 @@ class PrepSession: origin = _get_origin(anntype) if origin is typing.Union or origin is types.UnionType: - self.prep_union(cls, - attrname, - anntype, - recursion_level=recursion_level + 1) + self.prep_union( + cls, attrname, anntype, recursion_level=recursion_level + 1 + ) return if anntype is typing.Any: @@ -258,7 +274,8 @@ class PrepSession: if not isinstance(origin, type): raise TypeError( f'Unsupported type found for \'{attrname}\' on {cls}:' - f' {anntype}') + f' {anntype}' + ) # If a soft_default value/factory was passed, we do some basic # type checking on the top-level value here. We also run full @@ -298,12 +315,15 @@ class PrepSession: if len(childtypes) > 1: raise TypeError( f'Unrecognized typing arg count {len(childtypes)}' - f" for {anntype} attr '{attrname}' on {cls}") - self.prep_type(cls, - attrname, - childtypes[0], - ioattrs=None, - recursion_level=recursion_level + 1) + f" for {anntype} attr '{attrname}' on {cls}" + ) + self.prep_type( + cls, + attrname, + childtypes[0], + ioattrs=None, + recursion_level=recursion_level + 1, + ) return if origin is dict: @@ -324,18 +344,21 @@ class PrepSession: else: raise TypeError( f'Dict key type {childtypes[0]} for \'{attrname}\'' - f' on {cls.__name__} is not supported by dataclassio.') + f' on {cls.__name__} is not supported by dataclassio.' + ) # For value types we support any of our normal types. if not childtypes or _get_origin(childtypes[1]) is typing.Any: # 'Any' needs no further checks (just checked per-instance). pass else: - self.prep_type(cls, - attrname, - childtypes[1], - ioattrs=None, - recursion_level=recursion_level + 1) + self.prep_type( + cls, + attrname, + childtypes[1], + ioattrs=None, + recursion_level=recursion_level + 1, + ) return # For Tuples, simply check individual member types. @@ -346,18 +369,23 @@ class PrepSession: if not childtypes: raise TypeError( f'Tuple at \'{attrname}\'' - f' has no type args; dataclassio requires type args.') + f' has no type args; dataclassio requires type args.' + ) if childtypes[-1] is ...: - raise TypeError(f'Found ellipsis as part of type for' - f' \'{attrname}\' on {cls.__name__};' - f' these are not' - f' supported by dataclassio.') + raise TypeError( + f'Found ellipsis as part of type for' + f' \'{attrname}\' on {cls.__name__};' + f' these are not' + f' supported by dataclassio.' + ) for childtype in childtypes: - self.prep_type(cls, - attrname, - childtype, - ioattrs=None, - recursion_level=recursion_level + 1) + self.prep_type( + cls, + attrname, + childtype, + ioattrs=None, + recursion_level=recursion_level + 1, + ) return if issubclass(origin, Enum): @@ -376,26 +404,35 @@ class PrepSession: if origin is bytes: return - raise TypeError(f"Attr '{attrname}' on {cls.__name__} contains" - f" type '{anntype}'" - f' which is not supported by dataclassio.') + raise TypeError( + f"Attr '{attrname}' on {cls.__name__} contains" + f" type '{anntype}'" + f' which is not supported by dataclassio.' + ) - def prep_union(self, cls: type, attrname: str, anntype: Any, - recursion_level: int) -> None: + def prep_union( + self, cls: type, attrname: str, anntype: Any, recursion_level: int + ) -> None: """Run prep on a Union type.""" typeargs = typing.get_args(anntype) - if (len(typeargs) != 2 - or len([c for c in typeargs if c is type(None)]) != 1): # noqa - raise TypeError(f'Union {anntype} for attr \'{attrname}\' on' - f' {cls.__name__} is not supported by dataclassio;' - f' only 2 member Unions with one type being None' - f' are supported.') + if ( + len(typeargs) != 2 + or len([c for c in typeargs if c is type(None)]) != 1 + ): # noqa + raise TypeError( + f'Union {anntype} for attr \'{attrname}\' on' + f' {cls.__name__} is not supported by dataclassio;' + f' only 2 member Unions with one type being None' + f' are supported.' + ) for childtype in typeargs: - self.prep_type(cls, - attrname, - childtype, - None, - recursion_level=recursion_level + 1) + self.prep_type( + cls, + attrname, + childtype, + None, + recursion_level=recursion_level + 1, + ) def prep_enum(self, enumtype: type[Enum]) -> None: """Run prep on an enum type.""" @@ -406,13 +443,17 @@ class PrepSession: # find any others. for enumval in enumtype: if not isinstance(enumval.value, (str, int)): - raise TypeError(f'Enum value {enumval} has value type' - f' {type(enumval.value)}; only str and int is' - f' supported by dataclassio.') + raise TypeError( + f'Enum value {enumval} has value type' + f' {type(enumval.value)}; only str and int is' + f' supported by dataclassio.' + ) if valtype is None: valtype = type(enumval.value) else: if type(enumval.value) is not valtype: - raise TypeError(f'Enum type {enumtype} has multiple' - f' value types; dataclassio requires' - f' them to be uniform.') + raise TypeError( + f'Enum type {enumtype} has multiple' + f' value types; dataclassio requires' + f' them to be uniform.' + ) diff --git a/dist/ba_data/python/efro/dataclassio/extras.py b/dist/ba_data/python/efro/dataclassio/extras.py index cfa1992..327f829 100644 --- a/dist/ba_data/python/efro/dataclassio/extras.py +++ b/dist/ba_data/python/efro/dataclassio/extras.py @@ -40,8 +40,10 @@ def _diff(obj1: Any, obj2: Any, indent: int) -> str: assert dataclasses.is_dataclass(obj1) assert dataclasses.is_dataclass(obj2) if type(obj1) is not type(obj2): - raise TypeError(f'Passed objects are not of the same' - f' type ({type(obj1)} and {type(obj2)}).') + raise TypeError( + f'Passed objects are not of the same' + f' type ({type(obj1)} and {type(obj2)}).' + ) bits: list[str] = [] indentstr = ' ' * indent fields = dataclasses.fields(obj1) @@ -51,8 +53,11 @@ def _diff(obj1: Any, obj2: Any, indent: int) -> str: val2 = getattr(obj2, fieldname) # For nested dataclasses, dive in and do nice piecewise compares. - if (dataclasses.is_dataclass(val1) and dataclasses.is_dataclass(val2) - and type(val1) is type(val2)): + if ( + dataclasses.is_dataclass(val1) + and dataclasses.is_dataclass(val2) + and type(val1) is type(val2) + ): diff = _diff(val1, val2, indent + 2) if diff != '': bits.append(f'{indentstr}{fieldname}:') diff --git a/dist/ba_data/python/efro/debug.py b/dist/ba_data/python/efro/debug.py index 0b9b12a..8a78b54 100644 --- a/dist/ba_data/python/efro/debug.py +++ b/dist/ba_data/python/efro/debug.py @@ -1,6 +1,15 @@ # Released under the MIT License. See LICENSE for details. # -"""Utilities for debugging memory leaks or other issues.""" +"""Utilities for debugging memory leaks or other issues. + +IMPORTANT - these functions use the gc module which looks 'under the hood' +at Python and sometimes returns not-fully-initialized objects, which may +cause crashes or errors due to suddenly having references to them that they +didn't expect, etc. See https://github.com/python/cpython/issues/59313. +For this reason, these methods should NEVER be called in production code. +Enable them only for debugging situations and be aware that their use may +itself cause problems. The same is true for the gc module itself. +""" from __future__ import annotations import gc @@ -27,7 +36,9 @@ ABS_MAX_LEVEL = 10 # we're showing some temporary objects that we should be ignoring. -def getobjs(cls: type | str, contains: str | None = None) -> list[Any]: +def getobjs( + cls: type | str, contains: str | None = None, expanded: bool = False +) -> list[Any]: """Return all garbage-collected objects matching criteria. 'type' can be an actual type or a string in which case objects @@ -45,17 +56,51 @@ def getobjs(cls: type | str, contains: str | None = None) -> list[Any]: if not isinstance(contains, str | None): raise TypeError('Expected a string or None for contains') + allobjs = _get_all_objects(expanded=expanded) + if isinstance(cls, str): - objs = [o for o in gc.get_objects() if cls in str(type(o))] + objs = [o for o in allobjs if cls in str(type(o))] else: - objs = [o for o in gc.get_objects() if isinstance(o, cls)] + objs = [o for o in allobjs if isinstance(o, cls)] if contains is not None: objs = [o for o in objs if contains in str(o)] return objs -def getobj(objid: int) -> Any: +# Recursively expand slists objects into olist, using seen to track +# already processed objects. +def _getr(slist: list[Any], olist: list[Any], seen: set[int]) -> None: + for obj in slist: + if id(obj) in seen: + continue + seen.add(id(obj)) + olist.append(obj) + tll = gc.get_referents(obj) + if tll: + _getr(tll, olist, seen) + + +def _get_all_objects(expanded: bool) -> list[Any]: + """Return an expanded list of all objects. + + See https://utcc.utoronto.ca/~cks/space/blog/python/GetAllObjects + """ + gcl = gc.get_objects() + if not expanded: + return gcl + olist: list[Any] = [] + seen: set[int] = set() + # Just in case: + seen.add(id(gcl)) + seen.add(id(olist)) + seen.add(id(seen)) + # _getr does the real work. + _getr(gcl, olist, seen) + return olist + + +def getobj(objid: int, expanded: bool = False) -> Any: """Return a garbage-collected object by its id. Remember that this is VERY inefficient and should only ever be used @@ -65,7 +110,10 @@ def getobj(objid: int) -> Any: raise TypeError(f'Expected an int for objid; got a {type(objid)}.') # Don't wanna return stuff waiting to be garbage-collected. - for obj in gc.get_objects(): + gc.collect() + + allobjs = _get_all_objects(expanded=expanded) + for obj in allobjs: if id(obj) == objid: return obj raise RuntimeError(f'Object with id {objid} not found.') @@ -80,13 +128,15 @@ def getrefs(obj: Any) -> list[Any]: def printfiles(file: TextIO | None = None) -> None: """Print info about open files in the current app.""" import io + file = sys.stderr if file is None else file try: import psutil except ImportError: print( "Error: printfiles requires the 'psutil' module to be installed.", - file=file) + file=file, + ) return proc = psutil.Process() @@ -110,16 +160,20 @@ def printfiles(file: TextIO | None = None) -> None: textio_s = id(textio) if textio is not None else '' fileio = fileio_ids.get(ofile.fd) fileio_s = id(fileio) if fileio is not None else '' - print(f'#{i+1}: path={ofile.path!r},' - f' fd={ofile.fd}, mode={mode!r}, TextIOWrapper={textio_s},' - f' FileIO={fileio_s}') + print( + f'#{i+1}: path={ofile.path!r},' + f' fd={ofile.fd}, mode={mode!r}, TextIOWrapper={textio_s},' + f' FileIO={fileio_s}' + ) -def printrefs(obj: Any, - max_level: int = 2, - exclude_objs: list[Any] | None = None, - expand_ids: list[int] | None = None, - file: TextIO | None = None) -> None: +def printrefs( + obj: Any, + max_level: int = 2, + exclude_objs: list[Any] | None = None, + expand_ids: list[int] | None = None, + file: TextIO | None = None, +) -> None: """Print human readable list of objects referring to an object. 'max_level' specifies how many levels of recursion are printed. @@ -129,20 +183,24 @@ def printrefs(obj: Any, 'expand_ids' can be a list of object ids; if that particular object is found, it will always be expanded even if max_level has been reached. """ - _printrefs(obj, - level=0, - max_level=max_level, - exclude_objs=[] if exclude_objs is None else exclude_objs, - expand_ids=[] if expand_ids is None else expand_ids, - file=sys.stderr if file is None else file) + _printrefs( + obj, + level=0, + max_level=max_level, + exclude_objs=[] if exclude_objs is None else exclude_objs, + expand_ids=[] if expand_ids is None else expand_ids, + file=sys.stderr if file is None else file, + ) -def printtypes(limit: int = 50, file: TextIO | None = None) -> None: +def printtypes( + limit: int = 50, file: TextIO | None = None, expanded: bool = False +) -> None: """Print a human readable list of which types have the most instances.""" assert limit > 0 objtypes: dict[str, int] = {} gc.collect() # Recommended before get_objects(). - allobjs = gc.get_objects() + allobjs = _get_all_objects(expanded=expanded) allobjc = len(allobjs) for obj in allobjs: modname = type(obj).__module__ @@ -160,13 +218,45 @@ def printtypes(limit: int = 50, file: TextIO | None = None) -> None: print(f'Types most allocated ({allobjc} total objects):', file=file) for i, tpitem in enumerate( - sorted(objtypes.items(), key=lambda x: x[1], - reverse=True)[:limit]): + sorted(objtypes.items(), key=lambda x: x[1], reverse=True)[:limit] + ): tpname, tpval = tpitem percent = tpval / allobjc * 100.0 print(f'{i+1}: {tpname}: {tpval} ({percent:.2f}%)', file=file) +def printsizes( + limit: int = 50, file: TextIO | None = None, expanded: bool = False +) -> None: + """Print total allocated sizes of different types.""" + assert limit > 0 + objsizes: dict[str, int] = {} + gc.collect() # Recommended before get_objects(). + allobjs = _get_all_objects(expanded=expanded) + totalobjsize = 0 + + for obj in allobjs: + modname = type(obj).__module__ + tpname = type(obj).__qualname__ + if modname != 'builtins': + tpname = f'{modname}.{tpname}' + objsize = sys.getsizeof(obj) + objsizes[tpname] = objsizes.get(tpname, 0) + objsize + totalobjsize += objsize + + totalobjmb = totalobjsize / (1024 * 1024) + print( + f'Types with most allocated bytes ({totalobjmb:.2f} mb total):', + file=file, + ) + for i, tpitem in enumerate( + sorted(objsizes.items(), key=lambda x: x[1], reverse=True)[:limit] + ): + tpname, tpval = tpitem + percent = tpval / totalobjsize * 100.0 + print(f'{i+1}: {tpname}: {tpval} ({percent:.2f}%)', file=file) + + def _desctype(obj: Any) -> str: cls = type(obj) if cls is types.ModuleType: @@ -183,8 +273,13 @@ def _desc(obj: Any) -> str: # Print length and the first few types. tps = [_desctype(i) for i in obj[:3]] tpsj = ', '.join(tps) - tpss = (f', contains [{tpsj}, ...]' - if len(obj) > 3 else f', contains [{tpsj}]' if tps else '') + tpss = ( + f', contains [{tpsj}, ...]' + if len(obj) > 3 + else f', contains [{tpsj}]' + if tps + else '' + ) extra = f' (len {len(obj)}{tpss})' elif isinstance(obj, dict): # If it seems to be the vars() for a type or module, @@ -199,16 +294,27 @@ def _desc(obj: Any) -> str: f'{repr(n)}: {_desctype(v)}' for n, v in list(obj.items())[:3] ] pairsj = ', '.join(pairs) - pairss = (f', contains {{{pairsj}, ...}}' if len(obj) > 3 else - f', contains {{{pairsj}}}' if pairs else '') + pairss = ( + f', contains {{{pairsj}, ...}}' + if len(obj) > 3 + else f', contains {{{pairsj}}}' + if pairs + else '' + ) extra = f' (len {len(obj)}{pairss})' if extra is None: extra = '' return f'{_desctype(obj)} @ {id(obj)}{extra}' -def _printrefs(obj: Any, level: int, max_level: int, exclude_objs: list, - expand_ids: list[int], file: TextIO) -> None: +def _printrefs( + obj: Any, + level: int, + max_level: int, + exclude_objs: list, + expand_ids: list[int], + file: TextIO, +) -> None: ind = ' ' * level print(ind + _desc(obj), file=file) v = vars() @@ -233,9 +339,11 @@ def _printrefs(obj: Any, level: int, max_level: int, exclude_objs: list, # The 'refs' list we just made will be listed as a referrer # of this obj, so explicitly exclude it from the obj's listing. - _printrefs(ref, - level=level + 1, - max_level=max_level, - exclude_objs=exclude_objs + [refs], - expand_ids=expand_ids, - file=file) + _printrefs( + ref, + level=level + 1, + max_level=max_level, + exclude_objs=exclude_objs + [refs], + expand_ids=expand_ids, + file=file, + ) diff --git a/dist/ba_data/python/efro/error.py b/dist/ba_data/python/efro/error.py index e104558..7f13148 100644 --- a/dist/ba_data/python/efro/error.py +++ b/dist/ba_data/python/efro/error.py @@ -31,6 +31,7 @@ class CleanError(Exception): If the error has an empty message, prints nothing (not even a newline). """ from efro.terminal import Clr + errstr = str(self) if errstr: print(f'{Clr.SRED}{errstr}{Clr.RST}', flush=flush) @@ -95,9 +96,18 @@ def is_urllib_communication_error(exc: BaseException, url: str | None) -> bool: import urllib.error import http.client import socket - if isinstance(exc, (urllib.error.URLError, ConnectionError, - http.client.IncompleteRead, http.client.BadStatusLine, - http.client.RemoteDisconnected, socket.timeout)): + + if isinstance( + exc, + ( + urllib.error.URLError, + ConnectionError, + http.client.IncompleteRead, + http.client.BadStatusLine, + http.client.RemoteDisconnected, + socket.timeout, + ), + ): # Special case: although an HTTPError is a subclass of URLError, # we don't consider it a communication error. It generally means we @@ -120,14 +130,22 @@ def is_urllib_communication_error(exc: BaseException, url: str | None) -> bool: if exc.errno == 10051: # Windows unreachable network error. return True if exc.errno in { - errno.ETIMEDOUT, - errno.EHOSTUNREACH, - errno.ENETUNREACH, + errno.ETIMEDOUT, + errno.EHOSTUNREACH, + errno.ENETUNREACH, }: return True return False +def is_requests_communication_error(exc: BaseException) -> bool: + """Is the provided exception a communication-related error from requests?""" + import requests + + # Looks like this maps pretty well onto requests' ConnectionError + return isinstance(exc, requests.ConnectionError) + + def is_udp_communication_error(exc: BaseException) -> bool: """Should this udp-related exception be considered a communication error? @@ -144,18 +162,18 @@ def is_udp_communication_error(exc: BaseException) -> bool: if exc.errno == 10051: # Windows unreachable network error. return True if exc.errno in { - errno.EADDRNOTAVAIL, - errno.ETIMEDOUT, - errno.EHOSTUNREACH, - errno.ENETUNREACH, - errno.EINVAL, - errno.EPERM, - errno.EACCES, - # Windows 'invalid argument' error. - 10022, - # Windows 'a socket operation was attempted to' - # 'an unreachable network' error. - 10051, + errno.EADDRNOTAVAIL, + errno.ETIMEDOUT, + errno.EHOSTUNREACH, + errno.ENETUNREACH, + errno.EINVAL, + errno.EPERM, + errno.EACCES, + # Windows 'invalid argument' error. + 10022, + # Windows 'a socket operation was attempted to' + # 'an unreachable network' error. + 10051, }: return True return False @@ -172,11 +190,14 @@ def is_asyncio_streams_communication_error(exc: BaseException) -> bool: """ import ssl - if isinstance(exc, ( + if isinstance( + exc, + ( ConnectionError, TimeoutError, EOFError, - )): + ), + ): return True # Also some specific errno ones. @@ -184,9 +205,9 @@ def is_asyncio_streams_communication_error(exc: BaseException) -> bool: if exc.errno == 10051: # Windows unreachable network error. return True if exc.errno in { - errno.ETIMEDOUT, - errno.EHOSTUNREACH, - errno.ENETUNREACH, + errno.ETIMEDOUT, + errno.EHOSTUNREACH, + errno.ENETUNREACH, }: return True diff --git a/dist/ba_data/python/efro/log.py b/dist/ba_data/python/efro/log.py index 854fcc7..6ee2e89 100644 --- a/dist/ba_data/python/efro/log.py +++ b/dist/ba_data/python/efro/log.py @@ -30,6 +30,7 @@ class LogLevel(Enum): Note that these values are not currently interchangeable with the logging.ERROR, logging.DEBUG, etc. values. """ + DEBUG = 0 INFO = 1 WARNING = 2 @@ -42,7 +43,7 @@ LEVELNO_LOG_LEVELS = { logging.INFO: LogLevel.INFO, logging.WARNING: LogLevel.WARNING, logging.ERROR: LogLevel.ERROR, - logging.CRITICAL: LogLevel.CRITICAL + logging.CRITICAL: LogLevel.CRITICAL, } LEVELNO_COLOR_CODES: dict[int, tuple[str, str]] = { @@ -50,9 +51,12 @@ LEVELNO_COLOR_CODES: dict[int, tuple[str, str]] = { logging.INFO: ('', ''), logging.WARNING: (TerminalColor.YELLOW.value, TerminalColor.RESET.value), logging.ERROR: (TerminalColor.RED.value, TerminalColor.RESET.value), - logging.CRITICAL: - (TerminalColor.STRONG_MAGENTA.value + TerminalColor.BOLD.value + - TerminalColor.BG_BLACK.value, TerminalColor.RESET.value), + logging.CRITICAL: ( + TerminalColor.STRONG_MAGENTA.value + + TerminalColor.BOLD.value + + TerminalColor.BG_BLACK.value, + TerminalColor.RESET.value, + ), } @@ -60,8 +64,8 @@ LEVELNO_COLOR_CODES: dict[int, tuple[str, str]] = { @dataclass class LogEntry: """Single logged message.""" - name: Annotated[str, - IOAttrs('n', soft_default='root', store_default=False)] + + name: Annotated[str, IOAttrs('n', soft_default='root', store_default=False)] message: Annotated[str, IOAttrs('m')] level: Annotated[LogLevel, IOAttrs('l')] time: Annotated[datetime.datetime, IOAttrs('t')] @@ -95,15 +99,17 @@ class LogHandler(logging.Handler): # Otherwise we can get infinite loops as those prints come back to us # as new log entries. - def __init__(self, - path: str | Path | None, - echofile: TextIO | None, - suppress_non_root_debug: bool = False, - cache_size_limit: int = 0): + def __init__( + self, + path: str | Path | None, + echofile: TextIO | None, + suppress_non_root_debug: bool, + cache_size_limit: int, + cache_time_limit: datetime.timedelta | None, + ): super().__init__() # pylint: disable=consider-using-with - self._file = (None - if path is None else open(path, 'w', encoding='utf-8')) + self._file = None if path is None else open(path, 'w', encoding='utf-8') self._echofile = echofile self._callbacks_lock = Lock() self._callbacks: list[Callable[[LogEntry], None]] = [] @@ -111,11 +117,12 @@ class LogHandler(logging.Handler): self._file_chunks: dict[str, list[str]] = {'stdout': [], 'stderr': []} self._file_chunk_ship_task: dict[str, asyncio.Task | None] = { 'stdout': None, - 'stderr': None + 'stderr': None, } self._cache_size = 0 assert cache_size_limit >= 0 self._cache_size_limit = cache_size_limit + self._cache_time_limit = cache_time_limit self._cache: list[tuple[int, LogEntry]] = [] self._cache_index_offset = 0 self._cache_lock = Lock() @@ -145,6 +152,8 @@ class LogHandler(logging.Handler): asyncio.set_event_loop(self._event_loop) self._thread_bootstrapped = True try: + if self._cache_time_limit is not None: + self._event_loop.create_task(self._time_prune_cache()) self._event_loop.run_forever() except BaseException: # If this ever goes down we're in trouble. @@ -152,12 +161,35 @@ class LogHandler(logging.Handler): # Try to make some noise however we can. print('LogHandler died!!!', file=sys.stderr) import traceback + traceback.print_exc() raise - def get_cached(self, - start_index: int = 0, - max_entries: int | None = None) -> LogArchive: + async def _time_prune_cache(self) -> None: + assert self._cache_time_limit is not None + while bool(True): + await asyncio.sleep(61.27) + now = utc_now() + with self._cache_lock: + + # Quick out: if oldest cache entry is still valid, + # don't touch anything. + if ( + self._cache + and (now - self._cache[0][1].time) < self._cache_time_limit + ): + continue + + # Ok; full prune. + self._cache = [ + e + for e in self._cache + if (now - e[1].time) < self._cache_time_limit + ] + + def get_cached( + self, start_index: int = 0, max_entries: int | None = None + ) -> LogArchive: """Build and return an archive of cached log entries. This will only include entries that have been processed by the @@ -174,8 +206,11 @@ class LogHandler(logging.Handler): # Transform start_index to our present cache space. start_index -= self._cache_index_offset # Calc end-index in our present cache space. - end_index = (len(self._cache) - if max_entries is None else start_index + max_entries) + end_index = ( + len(self._cache) + if max_entries is None + else start_index + max_entries + ) # Clamp both indexes to both ends of our present space. start_index = max(0, min(start_index, len(self._cache))) @@ -184,7 +219,8 @@ class LogHandler(logging.Handler): return LogArchive( log_size=self._cache_index_offset + len(self._cache), start_index=start_index + self._cache_index_offset, - entries=[e[1] for e in self._cache[start_index:end_index]]) + entries=[e[1] for e in self._cache[start_index:end_index]], + ) def emit(self, record: logging.LogRecord) -> None: # Called by logging to send us records. @@ -196,8 +232,11 @@ class LogHandler(logging.Handler): # Special case - filter out this common extra-chatty category. # TODO - should use a standard logging.Filter for this. - if (self._suppress_non_root_debug and record.name != 'root' - and record.levelname == 'DEBUG'): + if ( + self._suppress_non_root_debug + and record.name != 'root' + and record.levelname == 'DEBUG' + ): return # We want to forward as much as we can along without processing it @@ -219,27 +258,40 @@ class LogHandler(logging.Handler): self._echofile.write(f'{msg}\n') self._event_loop.call_soon_threadsafe( - tpartial(self._emit_in_thread, record.name, record.levelno, - record.created, msg)) + tpartial( + self._emit_in_thread, + record.name, + record.levelno, + record.created, + msg, + ) + ) - def _emit_in_thread(self, name: str, levelno: int, created: float, - message: str) -> None: + def _emit_in_thread( + self, name: str, levelno: int, created: float, message: str + ) -> None: try: self._emit_entry( - LogEntry(name=name, - message=message, - level=LEVELNO_LOG_LEVELS.get(levelno, LogLevel.INFO), - time=datetime.datetime.fromtimestamp( - created, datetime.timezone.utc))) + LogEntry( + name=name, + message=message, + level=LEVELNO_LOG_LEVELS.get(levelno, LogLevel.INFO), + time=datetime.datetime.fromtimestamp( + created, datetime.timezone.utc + ), + ) + ) except Exception: import traceback + traceback.print_exc(file=self._echofile) def file_write(self, name: str, output: str) -> None: """Send raw stdout/stderr output to the logger to be collated.""" self._event_loop.call_soon_threadsafe( - tpartial(self._file_write_in_thread, name, output)) + tpartial(self._file_write_in_thread, name, output) + ) def _file_write_in_thread(self, name: str, output: str) -> None: try: @@ -263,19 +315,24 @@ class LogHandler(logging.Handler): # after a short bit if we never get a newline. ship_task = self._file_chunk_ship_task[name] if ship_task is None: - self._file_chunk_ship_task[name] = ( - self._event_loop.create_task( - self._ship_chunks_task(name))) + self._file_chunk_ship_task[ + name + ] = self._event_loop.create_task( + self._ship_chunks_task(name), + name='log ship file chunks', + ) except Exception: import traceback + traceback.print_exc(file=self._echofile) 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)) + tpartial(self._file_flush_in_thread, name) + ) def _file_flush_in_thread(self, name: str) -> None: try: @@ -287,10 +344,10 @@ class LogHandler(logging.Handler): except Exception: import traceback + traceback.print_exc(file=self._echofile) async def _ship_chunks_task(self, name: str) -> None: - await asyncio.sleep(0.1) self._ship_file_chunks(name, cancel_ship_task=False) def _ship_file_chunks(self, name: str, cancel_ship_task: bool) -> None: @@ -300,10 +357,10 @@ class LogHandler(logging.Handler): text = ''.join(self._file_chunks[name]).removesuffix('\n') self._emit_entry( - LogEntry(name=name, - message=text, - level=LogLevel.INFO, - time=utc_now())) + LogEntry( + name=name, message=text, level=LogLevel.INFO, time=utc_now() + ) + ) self._file_chunks[name] = [] ship_task = self._file_chunk_ship_task[name] if cancel_ship_task and ship_task is not None: @@ -319,8 +376,14 @@ class LogHandler(logging.Handler): # Do a rough calc of how many bytes this entry consumes. entry_size = sum( sys.getsizeof(x) - for x in (entry, entry.name, entry.message, entry.level, - entry.time)) + for x in ( + entry, + entry.name, + entry.message, + entry.level, + entry.time, + ) + ) self._cache.append((entry_size, entry)) self._cache_size += entry_size @@ -339,6 +402,7 @@ class LogHandler(logging.Handler): # Only print one callback error to avoid insanity. if not self._printed_callback_error: import traceback + traceback.print_exc(file=self._echofile) self._printed_callback_error = True @@ -353,8 +417,9 @@ class LogHandler(logging.Handler): class FileLogEcho: """A file-like object for forwarding stdout/stderr to a LogHandler.""" - def __init__(self, original: TextIO, name: str, - handler: LogHandler) -> None: + def __init__( + self, original: TextIO, name: str, handler: LogHandler + ) -> None: assert name in ('stdout', 'stderr') self._original = original self._name = name @@ -379,11 +444,14 @@ class FileLogEcho: return self._original.isatty() -def setup_logging(log_path: str | Path | None, - level: LogLevel, - suppress_non_root_debug: bool = False, - log_stdout_stderr: bool = False, - cache_size_limit: int = 0) -> LogHandler: +def setup_logging( + log_path: str | Path | None, + level: LogLevel, + suppress_non_root_debug: bool = False, + log_stdout_stderr: bool = False, + cache_size_limit: int = 0, + cache_time_limit: datetime.timedelta | None = None, +) -> LogHandler: """Set up our logging environment. Returns the custom handler which can be used to fetch information @@ -414,16 +482,20 @@ def setup_logging(log_path: str | Path | None, # echofile=sys.stderr if sys.stderr.isatty() else None, echofile=sys.stderr, suppress_non_root_debug=suppress_non_root_debug, - cache_size_limit=cache_size_limit) + cache_size_limit=cache_size_limit, + cache_time_limit=cache_time_limit, + ) # Note: going ahead with force=True here so that we replace any # existing logger. Though we warn if it looks like we are doing # that so we can try to avoid creating the first one. had_previous_handlers = bool(logging.root.handlers) - logging.basicConfig(level=lmap[level], - format='%(message)s', - handlers=[loghandler], - force=True) + logging.basicConfig( + level=lmap[level], + format='%(message)s', + handlers=[loghandler], + force=True, + ) if had_previous_handlers: logging.warning('setup_logging: force-replacing previous handlers.') @@ -431,8 +503,10 @@ def setup_logging(log_path: str | Path | None, # log entries from it. if log_stdout_stderr: sys.stdout = FileLogEcho( # type: ignore - sys.stdout, 'stdout', loghandler) + sys.stdout, 'stdout', loghandler + ) sys.stderr = FileLogEcho( # type: ignore - sys.stderr, 'stderr', loghandler) + sys.stderr, 'stderr', loghandler + ) return loghandler diff --git a/dist/ba_data/python/efro/message/__init__.py b/dist/ba_data/python/efro/message/__init__.py index 326ba02..ff67b2b 100644 --- a/dist/ba_data/python/efro/message/__init__.py +++ b/dist/ba_data/python/efro/message/__init__.py @@ -8,20 +8,36 @@ from __future__ import annotations from efro.util import set_canonical_module from efro.message._protocol import MessageProtocol -from efro.message._sender import (MessageSender, BoundMessageSender) -from efro.message._receiver import (MessageReceiver, BoundMessageReceiver) -from efro.message._module import (create_sender_module, create_receiver_module) -from efro.message._message import (Message, Response, SysResponse, - EmptySysResponse, ErrorSysResponse, - StringResponse, BoolResponse, - UnregisteredMessageIDError) +from efro.message._sender import MessageSender, BoundMessageSender +from efro.message._receiver import MessageReceiver, BoundMessageReceiver +from efro.message._module import create_sender_module, create_receiver_module +from efro.message._message import ( + Message, + Response, + SysResponse, + EmptySysResponse, + ErrorSysResponse, + StringResponse, + BoolResponse, + UnregisteredMessageIDError, +) __all__ = [ - 'Message', 'Response', 'SysResponse', 'EmptySysResponse', - 'ErrorSysResponse', 'StringResponse', 'BoolResponse', 'MessageProtocol', - 'MessageSender', 'BoundMessageSender', 'MessageReceiver', - 'BoundMessageReceiver', 'create_sender_module', 'create_receiver_module', - 'UnregisteredMessageIDError' + 'Message', + 'Response', + 'SysResponse', + 'EmptySysResponse', + 'ErrorSysResponse', + 'StringResponse', + 'BoolResponse', + 'MessageProtocol', + 'MessageSender', + 'BoundMessageSender', + 'MessageReceiver', + 'BoundMessageReceiver', + 'create_sender_module', + 'create_receiver_module', + 'UnregisteredMessageIDError', ] # Have these things present themselves cleanly as 'thismodule.SomeClass' diff --git a/dist/ba_data/python/efro/message/_message.py b/dist/ba_data/python/efro/message/_message.py index 7a00070..9343a39 100644 --- a/dist/ba_data/python/efro/message/_message.py +++ b/dist/ba_data/python/efro/message/_message.py @@ -57,10 +57,12 @@ class ErrorSysResponse(SysResponse): class ErrorType(Enum): """Type of error that occurred while sending a message.""" + REMOTE = 0 REMOTE_CLEAN = 1 LOCAL = 2 COMMUNICATION = 3 + REMOTE_COMMUNICATION = 4 error_message: Annotated[str, IOAttrs('m')] error_type: Annotated[ErrorType, IOAttrs('e')] = ErrorType.REMOTE diff --git a/dist/ba_data/python/efro/message/_module.py b/dist/ba_data/python/efro/message/_module.py index 16c0982..2fa91fa 100644 --- a/dist/ba_data/python/efro/message/_module.py +++ b/dist/ba_data/python/efro/message/_module.py @@ -38,20 +38,23 @@ def create_sender_module( If 'private' is True, class-names will be prefixed with an '_'. - Note that generated line lengths are not clipped, so output may need - to be run through a formatter to prevent lint warnings about excessive - line lengths. + Note: output code may have long lines and should generally be run + through a formatter. We should perhaps move this functionality to + efrotools so we can include that functionality inline. """ protocol = _protocol_from_code( - build_time_protocol_create_code if build_time_protocol_create_code - is not None else protocol_create_code) + build_time_protocol_create_code + if build_time_protocol_create_code is not None + else protocol_create_code + ) return protocol.do_create_sender_module( basename=basename, protocol_create_code=protocol_create_code, enable_sync_sends=enable_sync_sends, enable_async_sends=enable_async_sends, private=private, - protocol_module_level_import_code=protocol_module_level_import_code) + protocol_module_level_import_code=protocol_module_level_import_code, + ) def create_receiver_module( @@ -62,7 +65,7 @@ def create_receiver_module( protocol_module_level_import_code: str | None = None, build_time_protocol_create_code: str | None = None, ) -> str: - """"Create a Python module defining a MessageReceiver subclass. + """ "Create a Python module defining a MessageReceiver subclass. This class is primarily for type checking and will contain overrides for the register method for message/response types defined in @@ -81,14 +84,17 @@ def create_receiver_module( line lengths. """ protocol = _protocol_from_code( - build_time_protocol_create_code if build_time_protocol_create_code - is not None else protocol_create_code) + build_time_protocol_create_code + if build_time_protocol_create_code is not None + else protocol_create_code + ) return protocol.do_create_receiver_module( basename=basename, protocol_create_code=protocol_create_code, is_async=is_async, private=private, - protocol_module_level_import_code=protocol_module_level_import_code) + protocol_module_level_import_code=protocol_module_level_import_code, + ) def _protocol_from_code(protocol_create_code: str) -> MessageProtocol: @@ -98,5 +104,6 @@ def _protocol_from_code(protocol_create_code: str) -> MessageProtocol: if not isinstance(protocol, MessageProtocol): raise RuntimeError( f'protocol_create_code yielded' - f' a {type(protocol)}; expected a MessageProtocol instance.') + f' a {type(protocol)}; expected a MessageProtocol instance.' + ) return protocol diff --git a/dist/ba_data/python/efro/message/_protocol.py b/dist/ba_data/python/efro/message/_protocol.py index 86387de..3c24c5b 100644 --- a/dist/ba_data/python/efro/message/_protocol.py +++ b/dist/ba_data/python/efro/message/_protocol.py @@ -8,15 +8,22 @@ from __future__ import annotations from typing import TYPE_CHECKING import traceback -import logging import json -from efro.error import CleanError -from efro.dataclassio import (is_ioprepped_dataclass, dataclass_to_dict, - dataclass_from_dict) -from efro.message._message import (Message, Response, SysResponse, - ErrorSysResponse, EmptySysResponse, - UnregisteredMessageIDError) +from efro.error import CleanError, CommunicationError +from efro.dataclassio import ( + is_ioprepped_dataclass, + dataclass_to_dict, + dataclass_from_dict, +) +from efro.message._message import ( + Message, + Response, + SysResponse, + ErrorSysResponse, + EmptySysResponse, + UnregisteredMessageIDError, +) if TYPE_CHECKING: from typing import Any, Literal @@ -31,41 +38,72 @@ class MessageProtocol: etc. """ - def __init__(self, - message_types: dict[int, type[Message]], - response_types: dict[int, type[Response]], - forward_clean_errors: bool = False, - remote_errors_include_stack_traces: bool = False) -> None: + def __init__( + self, + message_types: dict[int, type[Message]], + response_types: dict[int, type[Response]], + forward_communication_errors: bool = False, + forward_clean_errors: bool = False, + remote_errors_include_stack_traces: bool = False, + log_remote_errors: bool = True, + ) -> None: """Create a protocol with a given configuration. - Note that common response types are automatically registered - with (unchanging negative ids) so they don't need to be passed - explicitly (but can be if a different id is desired). + If 'forward_communication_errors' is True, + efro.error.CommunicationErrors raised on the receiver end will + result in a matching error raised back on the sender. This can + be useful if the receiver will be in some way forwarding + messages along and the sender doesn't need to know where + communication breakdowns occurred; only that they did. If 'forward_clean_errors' is True, efro.error.CleanError exceptions raised on the receiver end will result in a matching - CleanError raised back on the sender. All other Exception types - come across as efro.error.RemoteError. + CleanError raised back on the sender. - If 'remote_errors_include_stack_traces' is True, stringified stack - traces will be returned to the sender for exceptions occurring - on the receiver end. This can make debugging easier but should - only be used when the client is trusted to see such info. + 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). + + If 'remote_errors_include_stack_traces' is True, stringified + stack traces will be returned with efro.error.RemoteError + exceptions. This is useful for debugging but should only be + enabled in cases where the sender is trusted to see internal + details of the receiver. + + By default, when a message-handling exception will result in an + efro.error.RemoteError being returned to the sender, the + exception will be logged on the receiver. This is because the + 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 + 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 + aware, however, that in that case it may be possible for + communication errors to prevent such error messages from + ever being seen. """ + # pylint: disable=too-many-locals self.message_types_by_id: dict[int, type[Message]] = {} self.message_ids_by_type: dict[type[Message], int] = {} - self.response_types_by_id: dict[int, type[Response] - | type[SysResponse]] = {} - self.response_ids_by_type: dict[type[Response] | type[SysResponse], - int] = {} + self.response_types_by_id: dict[ + int, type[Response] | type[SysResponse] + ] = {} + self.response_ids_by_type: dict[ + type[Response] | type[SysResponse], int + ] = {} for m_id, m_type in message_types.items(): # Make sure only valid message types were passed and each # id was assigned only once. assert isinstance(m_id, int) assert m_id >= 0 - assert (is_ioprepped_dataclass(m_type) - and issubclass(m_type, Message)) + assert is_ioprepped_dataclass(m_type) and issubclass( + m_type, Message + ) assert self.message_types_by_id.get(m_id) is None self.message_types_by_id[m_id] = m_type self.message_ids_by_type[m_type] = m_id @@ -73,8 +111,9 @@ class MessageProtocol: for r_id, r_type in response_types.items(): assert isinstance(r_id, int) assert r_id >= 0 - assert (is_ioprepped_dataclass(r_type) - and issubclass(r_type, Response)) + assert is_ioprepped_dataclass(r_type) and issubclass( + r_type, Response + ) assert self.response_types_by_id.get(r_id) is None self.response_types_by_id[r_id] = r_type self.response_ids_by_type[r_type] = r_id @@ -98,8 +137,9 @@ class MessageProtocol: m_rtypes = m_type.get_response_types() assert isinstance(m_rtypes, list) - assert m_rtypes, ( - f'Message type {m_type} specifies no return types.') + assert ( + m_rtypes + ), f'Message type {m_type} specifies no return types.' assert len(set(m_rtypes)) == len(m_rtypes) # check dups for m_rtype in m_rtypes: all_response_types.add(m_rtype) @@ -111,7 +151,8 @@ class MessageProtocol: if cls not in self.response_ids_by_type: raise ValueError( f'Possible response type {cls} needs to be included' - f' in response_types for this protocol.') + f' in response_types for this protocol.' + ) # Make sure all registered types have unique base names. # We can take advantage of this to generate cleaner looking @@ -120,11 +161,15 @@ class MessageProtocol: if len(mtypenames) != len(message_types): raise ValueError( 'message_types contains duplicate __name__s;' - ' all types are required to have unique names.') + ' all types are required to have unique names.' + ) self.forward_clean_errors = forward_clean_errors + self.forward_communication_errors = forward_communication_errors self.remote_errors_include_stack_traces = ( - remote_errors_include_stack_traces) + remote_errors_include_stack_traces + ) + self.log_remote_errors = log_remote_errors @staticmethod def encode_dict(obj: dict) -> str: @@ -139,32 +184,56 @@ class MessageProtocol: """Encode a response to a json ready dict.""" return self._to_dict(response, self.response_ids_by_type, 'response') - def error_to_response(self, exc: Exception) -> SysResponse: - """Translate an error to a response.""" + def error_to_response(self, exc: Exception) -> tuple[SysResponse, bool]: + """Translate an Exception to a SysResponse. - # Log any errors we got during handling. - logging.exception('Error in efro.message handling.') + Also returns whether the error should be logged if this happened + within handle_raw_message(). + """ # If anything goes wrong, return a ErrorSysResponse instead. # (either CLEAN or generic REMOTE) - if isinstance(exc, CleanError) and self.forward_clean_errors: - return ErrorSysResponse( - error_message=str(exc), - error_type=ErrorSysResponse.ErrorType.REMOTE_CLEAN) - return ErrorSysResponse( - error_message=(traceback.format_exc() - if self.remote_errors_include_stack_traces else - 'An internal error has occurred.'), - error_type=ErrorSysResponse.ErrorType.REMOTE) + if self.forward_clean_errors and isinstance(exc, CleanError): + return ( + ErrorSysResponse( + error_message=str(exc), + error_type=ErrorSysResponse.ErrorType.REMOTE_CLEAN, + ), + False, + ) + if self.forward_communication_errors and isinstance( + exc, CommunicationError + ): + return ( + ErrorSysResponse( + error_message=str(exc), + error_type=ErrorSysResponse.ErrorType.REMOTE_COMMUNICATION, + ), + False, + ) + return ( + ErrorSysResponse( + error_message=( + traceback.format_exc() + if self.remote_errors_include_stack_traces + else 'An internal error has occurred.' + ), + error_type=ErrorSysResponse.ErrorType.REMOTE, + ), + self.log_remote_errors, + ) - def _to_dict(self, message: Any, ids_by_type: dict[type, int], - opname: str) -> dict: + def _to_dict( + self, message: Any, ids_by_type: dict[type, int], opname: str + ) -> dict: """Encode a message to a json string for transport.""" m_id: int | None = ids_by_type.get(type(message)) if m_id is None: - raise TypeError(f'{opname} type is not registered in protocol:' - f' {type(message)}') + raise TypeError( + f'{opname} type is not registered in protocol:' + f' {type(message)}' + ) out = {'t': m_id, 'm': dataclass_to_dict(message)} return out @@ -189,8 +258,9 @@ class MessageProtocol: # Weeeird; we get mypy errors returning dict[int, type] but # dict[int, typing.Type] or dict[int, type[Any]] works.. - def _from_dict(self, data: dict, types_by_id: dict[int, type[Any]], - opname: str) -> Any: + def _from_dict( + self, data: dict, types_by_id: dict[int, type[Any]], opname: str + ) -> Any: """Decode a message from a json string.""" msgdict: dict | None @@ -205,15 +275,19 @@ class MessageProtocol: msgtype = types_by_id.get(m_id) if msgtype is None: raise UnregisteredMessageIDError( - f'Got unregistered {opname} id of {m_id}.') + f'Got unregistered {opname} id of {m_id}.' + ) return dataclass_from_dict(msgtype, msgdict) - def _get_module_header(self, - part: Literal['sender', 'receiver'], - extra_import_code: str | None = None) -> str: + def _get_module_header( + self, + part: Literal['sender', 'receiver'], + extra_import_code: str | None = None, + ) -> str: """Return common parts of generated modules.""" # pylint: disable=too-many-locals, too-many-branches import textwrap + tpimports: dict[str, list[str]] = {} imports: dict[str, list[str]] = {} @@ -223,8 +297,9 @@ class MessageProtocol: if part == 'sender': msgtypes.append(Message) for msgtype in msgtypes: - tpimports.setdefault(msgtype.__module__, - []).append(msgtype.__name__) + tpimports.setdefault(msgtype.__module__, []).append( + msgtype.__name__ + ) rsptypes = list(self.response_ids_by_type) if part == 'sender': rsptypes.append(Response) @@ -232,15 +307,20 @@ class MessageProtocol: # Skip these as they don't actually show up in code. if rsp_tp is EmptySysResponse or rsp_tp is ErrorSysResponse: continue - if (single_message_type and part == 'sender' - and rsp_tp is not Response): + if ( + single_message_type + and part == 'sender' + and rsp_tp is not Response + ): # We need to cast to the single supported response type # in this case so need response types at runtime. - imports.setdefault(rsp_tp.__module__, - []).append(rsp_tp.__name__) + imports.setdefault(rsp_tp.__module__, []).append( + rsp_tp.__name__ + ) else: - tpimports.setdefault(rsp_tp.__module__, - []).append(rsp_tp.__name__) + tpimports.setdefault(rsp_tp.__module__, []).append( + rsp_tp.__name__ + ) import_lines = '' tpimport_lines = '' @@ -261,16 +341,21 @@ class MessageProtocol: tpimport_lines += f'{line}\n' if part == 'sender': - import_lines += ('from efro.message import MessageSender,' - ' BoundMessageSender') + import_lines += ( + 'from efro.message import MessageSender,' ' BoundMessageSender' + ) tpimport_typing_extras = '' else: if single_message_type: - import_lines += ('from efro.message import (MessageReceiver,' - ' BoundMessageReceiver, Message, Response)') + import_lines += ( + 'from efro.message import (MessageReceiver,' + ' BoundMessageReceiver, Message, Response)' + ) else: - import_lines += ('from efro.message import MessageReceiver,' - ' BoundMessageReceiver') + import_lines += ( + 'from efro.message import MessageReceiver,' + ' BoundMessageReceiver' + ) tpimport_typing_extras = ', Awaitable' if extra_import_code is not None: @@ -283,32 +368,35 @@ class MessageProtocol: if part == 'receiver': baseimps.append('Callable') baseimps_s = ', '.join(baseimps) - out = ('# Released under the MIT License. See LICENSE for details.\n' - f'#\n' - f'"""Auto-generated {part} module. Do not edit by hand."""\n' - f'\n' - f'from __future__ import annotations\n' - f'\n' - f'from typing import TYPE_CHECKING{ovld}\n' - f'\n' - f'{import_lines}\n' - f'\n' - f'if TYPE_CHECKING:\n' - f' from typing import {baseimps_s}' - f'{tpimport_typing_extras}\n' - f'{tpimport_lines}' - f'\n' - f'\n') + out = ( + '# Released under the MIT License. See LICENSE for details.\n' + f'#\n' + f'"""Auto-generated {part} module. Do not edit by hand."""\n' + f'\n' + f'from __future__ import annotations\n' + f'\n' + f'from typing import TYPE_CHECKING{ovld}\n' + f'\n' + f'{import_lines}\n' + f'\n' + f'if TYPE_CHECKING:\n' + f' from typing import {baseimps_s}' + f'{tpimport_typing_extras}\n' + f'{tpimport_lines}' + f'\n' + f'\n' + ) return out def do_create_sender_module( - self, - basename: str, - protocol_create_code: str, - enable_sync_sends: bool, - enable_async_sends: bool, - private: bool = False, - protocol_module_level_import_code: str | None = None) -> str: + self, + basename: str, + protocol_create_code: str, + enable_sync_sends: bool, + enable_async_sends: bool, + private: bool = False, + protocol_module_level_import_code: str | None = None, + ) -> str: """Used by create_sender_module(); do not call directly.""" # pylint: disable=too-many-locals import textwrap @@ -317,25 +405,26 @@ class MessageProtocol: ppre = '_' if private else '' out = self._get_module_header( - 'sender', extra_import_code=protocol_module_level_import_code) + 'sender', extra_import_code=protocol_module_level_import_code + ) ccind = textwrap.indent(protocol_create_code, ' ') - out += (f'class {ppre}{basename}(MessageSender):\n' - f' """Protocol-specific sender."""\n' - f'\n' - f' def __init__(self) -> None:\n' - f'{ccind}\n' - f' super().__init__(protocol)\n' - f'\n' - f' def __get__(self,\n' - f' obj: Any,\n' - f' type_in: Any = None)' - f' -> {ppre}Bound{basename}:\n' - f' return {ppre}Bound{basename}' - f'(obj, self)\n' - f'\n' - f'\n' - f'class {ppre}Bound{basename}(BoundMessageSender):\n' - f' """Protocol-specific bound sender."""\n') + out += ( + f'class {ppre}{basename}(MessageSender):\n' + f' """Protocol-specific sender."""\n' + f'\n' + f' def __init__(self) -> None:\n' + f'{ccind}\n' + f' super().__init__(protocol)\n' + f'\n' + f' def __get__(\n' + f' self, obj: Any, type_in: Any = None\n' + f' ) -> {ppre}Bound{basename}:\n' + f' return {ppre}Bound{basename}(obj, self)\n' + f'\n' + f'\n' + f'class {ppre}Bound{basename}(BoundMessageSender):\n' + f' """Protocol-specific bound sender."""\n' + ) def _filt_tp_name(rtype: type[Response] | None) -> str: return 'None' if rtype is None else rtype.__name__ @@ -362,15 +451,17 @@ class MessageProtocol: rtypevar = ' | '.join(_filt_tp_name(t) for t in rtypes) else: rtypevar = _filt_tp_name(rtypes[0]) - out += (f'\n' - f' {pfx}def send{sfx}(self,' - f' message: {msgtypevar})' - f' -> {rtypevar}:\n' - f' """Send a message {how}."""\n' - f' out = {awt}self._sender.' - f'send{sfx}(self._obj, message)\n' - f' assert isinstance(out, {rtypevar})\n' - f' return out\n') + out += ( + f'\n' + f' {pfx}def send{sfx}(self,' + f' message: {msgtypevar})' + f' -> {rtypevar}:\n' + f' """Send a message {how}."""\n' + f' out = {awt}self._sender.' + f'send{sfx}(self._obj, message)\n' + f' assert isinstance(out, {rtypevar})\n' + f' return out\n' + ) else: for msgtype in msgtypes: @@ -379,31 +470,37 @@ class MessageProtocol: rtypes = msgtype.get_response_types() if len(rtypes) > 1: rtypevar = ' | '.join( - _filt_tp_name(t) for t in rtypes) + _filt_tp_name(t) for t in rtypes + ) else: rtypevar = _filt_tp_name(rtypes[0]) - out += (f'\n' - f' @overload\n' - f' {pfx}def send{sfx}(self,' - f' message: {msgtypevar})' - f' -> {rtypevar}:\n' - f' ...\n') - out += (f'\n' - f' {pfx}def send{sfx}(self, message: Message)' - f' -> Response | None:\n' - f' """Send a message {how}."""\n' - f' return {awt}self._sender.' - f'send{sfx}(self._obj, message)\n') + out += ( + f'\n' + f' @overload\n' + f' {pfx}def send{sfx}(self,' + f' message: {msgtypevar})' + f' -> {rtypevar}:\n' + f' ...\n' + ) + out += ( + f'\n' + f' {pfx}def send{sfx}(self, message: Message)' + f' -> Response | None:\n' + f' """Send a message {how}."""\n' + f' return {awt}self._sender.' + f'send{sfx}(self._obj, message)\n' + ) return out def do_create_receiver_module( - self, - basename: str, - protocol_create_code: str, - is_async: bool, - private: bool = False, - protocol_module_level_import_code: str | None = None) -> str: + self, + basename: str, + protocol_create_code: str, + is_async: bool, + private: bool = False, + protocol_module_level_import_code: str | None = None, + ) -> str: """Used by create_receiver_module(); do not call directly.""" # pylint: disable=too-many-locals import textwrap @@ -412,24 +509,27 @@ class MessageProtocol: ppre = '_' if private else '' msgtypes = list(self.message_ids_by_type.keys()) out = self._get_module_header( - 'receiver', extra_import_code=protocol_module_level_import_code) + 'receiver', extra_import_code=protocol_module_level_import_code + ) ccind = textwrap.indent(protocol_create_code, ' ') - out += (f'class {ppre}{basename}(MessageReceiver):\n' - f' """Protocol-specific {desc} receiver."""\n' - f'\n' - f' is_async = {is_async}\n' - f'\n' - f' def __init__(self) -> None:\n' - f'{ccind}\n' - f' super().__init__(protocol)\n' - f'\n' - f' def __get__(\n' - f' self,\n' - f' obj: Any,\n' - f' type_in: Any = None,\n' - f' ) -> {ppre}Bound{basename}:\n' - f' return {ppre}Bound{basename}(' - f'obj, self)\n') + out += ( + f'class {ppre}{basename}(MessageReceiver):\n' + f' """Protocol-specific {desc} receiver."""\n' + f'\n' + f' is_async = {is_async}\n' + f'\n' + f' def __init__(self) -> None:\n' + f'{ccind}\n' + f' super().__init__(protocol)\n' + f'\n' + f' def __get__(\n' + f' self,\n' + f' obj: Any,\n' + f' type_in: Any = None,\n' + f' ) -> {ppre}Bound{basename}:\n' + f' return {ppre}Bound{basename}(' + f'obj, self)\n' + ) # Define handler() overloads for all registered message types. @@ -460,9 +560,11 @@ class MessageProtocol: f' -> Callable[[Any, {msgtypevar}], {rtypevar}]:\n' f' """Decorator to register message handlers."""\n' f' from typing import cast, Callable, Any\n' + f'\n' f' self.register_handler(cast(Callable' f'[[Any, Message], Response], call))\n' - f' return call\n') + f' return call\n' + ) else: for msgtype in msgtypes: msgtypevar = msgtype.__name__ @@ -472,49 +574,54 @@ class MessageProtocol: else: rtypevar = _filt_tp_name(rtypes[0]) rtypevar = f'{cbgn}{rtypevar}{cend}' - out += (f'\n' - f' @overload\n' - f' def handler(\n' - f' self,\n' - f' call: Callable[[Any, {msgtypevar}], ' - f'{rtypevar}],\n' - f' )' - f' -> Callable[[Any, {msgtypevar}], {rtypevar}]:\n' - f' ...\n') + out += ( + f'\n' + f' @overload\n' + f' def handler(\n' + f' self,\n' + f' call: Callable[[Any, {msgtypevar}], ' + f'{rtypevar}],\n' + f' )' + f' -> Callable[[Any, {msgtypevar}], {rtypevar}]:\n' + f' ...\n' + ) out += ( '\n' ' def handler(self, call: Callable) -> Callable:\n' ' """Decorator to register message handlers."""\n' ' self.register_handler(call)\n' - ' return call\n') + ' return call\n' + ) - out += (f'\n' - f'\n' - f'class {ppre}Bound{basename}(BoundMessageReceiver):\n' - f' """Protocol-specific bound receiver."""\n') + out += ( + f'\n' + f'\n' + f'class {ppre}Bound{basename}(BoundMessageReceiver):\n' + f' """Protocol-specific bound receiver."""\n' + ) if is_async: out += ( '\n' - ' async def handle_raw_message(self,\n' - ' message: str,\n' - ' raise_unregistered: bool =' - ' False) -> str:\n' + ' async def handle_raw_message(\n' + ' self, message: str, raise_unregistered: bool = False\n' + ' ) -> str:\n' ' """Asynchronously handle a raw incoming message."""\n' - ' return await self._receiver.handle_raw_message_async(' - '\n' - ' self._obj, message, raise_unregistered)\n') + ' return await self._receiver.' + 'handle_raw_message_async(\n' + ' self._obj, message, raise_unregistered\n' + ' )\n' + ) else: out += ( '\n' - ' def handle_raw_message(self,\n' - ' message: str,\n' - ' raise_unregistered: bool = False)' - ' -> str:\n' + ' def handle_raw_message(\n' + ' self, message: str, raise_unregistered: bool = False\n' + ' ) -> str:\n' ' """Synchronously handle a raw incoming message."""\n' - ' return self._receiver.handle_raw_message(' - 'self._obj, message,\n' - ' ' - 'raise_unregistered)\n') + ' return self._receiver.handle_raw_message(\n' + ' self._obj, message, raise_unregistered\n' + ' )\n' + ) return out diff --git a/dist/ba_data/python/efro/message/_receiver.py b/dist/ba_data/python/efro/message/_receiver.py index 5b23a80..e4c9244 100644 --- a/dist/ba_data/python/efro/message/_receiver.py +++ b/dist/ba_data/python/efro/message/_receiver.py @@ -11,8 +11,12 @@ import inspect import logging from typing import TYPE_CHECKING -from efro.message._message import (Message, Response, EmptySysResponse, - UnregisteredMessageIDError) +from efro.message._message import ( + Message, + Response, + EmptySysResponse, + UnregisteredMessageIDError, +) if TYPE_CHECKING: from typing import Any, Callable, Awaitable @@ -51,20 +55,23 @@ class MessageReceiver: def __init__(self, protocol: MessageProtocol) -> None: self.protocol = protocol self._handlers: dict[type[Message], Callable] = {} - self._decode_filter_call: Callable[[Any, dict, Message], - None] | None = None + self._decode_filter_call: Callable[ + [Any, dict, Message], None + ] | None = None self._encode_filter_call: Callable[ - [Any, Message | None, Response | SysResponse, dict], - None] | None = None + [Any, Message | None, Response | SysResponse, dict], None + ] | None = None # TODO: don't currently have async encode equivalent # or either for sender; can add as needed. - self._decode_filter_async_call: Callable[[Any, dict, Message], - Awaitable[None]] | None = None + self._decode_filter_async_call: Callable[ + [Any, dict, Message], Awaitable[None] + ] | None = None # noinspection PyProtectedMember def register_handler( - self, call: Callable[[Any, Message], Response | None]) -> None: + self, call: Callable[[Any, Message], Response | None] + ) -> None: """Register a handler call. The message type handled by the call is determined by its @@ -82,15 +89,20 @@ class MessageReceiver: # The provided callable should be a method taking one 'msg' arg. expectedsig = ['self', 'msg'] if sig.args != expectedsig: - raise ValueError(f'Expected callable signature of {expectedsig};' - f' got {sig.args}') + raise ValueError( + f'Expected callable signature of {expectedsig};' + f' got {sig.args}' + ) # Make sure we are only given async methods if we are an async handler # and sync ones otherwise. 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.') + 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. @@ -104,7 +116,8 @@ class MessageReceiver: msgtype = anns.get('msg') if not isinstance(msgtype, type): raise TypeError( - f'expected a type for "msg" annotation; got {type(msgtype)}.') + f'expected a type for "msg" annotation; got {type(msgtype)}.' + ) assert issubclass(msgtype, Message) ret = anns.get('return') @@ -114,21 +127,26 @@ class MessageReceiver: if isinstance(ret, (_GenericAlias, types.UnionType)): targs = get_args(ret) if not all(isinstance(a, (type, type(None))) for a in targs): - raise TypeError(f'expected only types for "return" annotation;' - f' got {targs}.') + raise TypeError( + f'expected only types for "return" annotation;' + f' got {targs}.' + ) responsetypes = targs else: if not isinstance(ret, (type, type(None))): - raise TypeError(f'expected one or more types for' - f' "return" annotation; got a {type(ret)}.') + raise TypeError( + f'expected one or more types for' + f' "return" annotation; got a {type(ret)}.' + ) # This seems like maybe a mypy bug. Appeared after adding # types.UnionType above. - responsetypes = (ret, ) + responsetypes = (ret,) # This will contain NoneType for empty return cases, but # we expect it to be None. - responsetypes = tuple(None if r is type(None) else r - for r in responsetypes) + responsetypes = tuple( + None if r is type(None) else r for r in responsetypes + ) # Make sure our protocol has this message type registered and our # return types exactly match. (Technically we could return a subset @@ -137,19 +155,23 @@ class MessageReceiver: registered_types = self.protocol.message_ids_by_type.keys() if msgtype not in registered_types: - raise TypeError(f'Message type {msgtype} is not registered' - f' in this Protocol.') + raise TypeError( + f'Message type {msgtype} is not registered' + f' in this Protocol.' + ) if msgtype in self._handlers: - raise TypeError(f'Message type {msgtype} already has a registered' - f' handler.') + raise TypeError( + f'Message type {msgtype} already has a registered' f' handler.' + ) # Make sure the responses exactly matches what the message expects. if set(responsetypes) != set(msgtype.get_response_types()): raise TypeError( f'Provided response types {responsetypes} do not' f' match the set expected by message type {msgtype}: ' - f'({msgtype.get_response_types()})') + f'({msgtype.get_response_types()})' + ) # Ok; we're good! self._handlers[msgtype] = call @@ -182,8 +204,9 @@ class MessageReceiver: def encode_filter_method( self, - call: Callable[[Any, Message | None, Response | SysResponse, dict], - None] + call: Callable[ + [Any, Message | None, Response | SysResponse, dict], None + ], ) -> Callable[[Any, Message | None, Response, dict], None]: """Function decorator for defining an encode filter. @@ -200,15 +223,18 @@ class MessageReceiver: if issubclass(msgtype, Response): continue if msgtype not in self._handlers: - msg = (f'Protocol message type {msgtype} is not handled' - f' by receiver type {type(self)}.') + msg = ( + f'Protocol message type {msgtype} is not handled' + f' by receiver type {type(self)}.' + ) if log_only: logging.error(msg) else: raise TypeError(msg) - def _decode_incoming_message_base(self, bound_obj: Any, - msg: str) -> tuple[Any, dict, Message]: + def _decode_incoming_message_base( + self, bound_obj: Any, msg: str + ) -> tuple[Any, dict, Message]: # Decode the incoming message. msg_dict = self.protocol.decode_dict(msg) msg_decoded = self.protocol.message_from_dict(msg_dict) @@ -218,8 +244,9 @@ class MessageReceiver: return bound_obj, msg_dict, msg_decoded def _decode_incoming_message(self, bound_obj: Any, msg: str) -> Message: - bound_obj, _msg_dict, msg_decoded = ( - self._decode_incoming_message_base(bound_obj=bound_obj, msg=msg)) + bound_obj, _msg_dict, msg_decoded = self._decode_incoming_message_base( + bound_obj=bound_obj, msg=msg + ) # If they've set an async filter but are calling sync # handle_raw_message() its likely a bug. @@ -227,24 +254,29 @@ class MessageReceiver: return msg_decoded - async def _decode_incoming_message_async(self, bound_obj: Any, - msg: str) -> Message: - bound_obj, msg_dict, msg_decoded = (self._decode_incoming_message_base( - bound_obj=bound_obj, msg=msg)) + async def _decode_incoming_message_async( + self, bound_obj: Any, msg: str + ) -> Message: + bound_obj, msg_dict, msg_decoded = self._decode_incoming_message_base( + bound_obj=bound_obj, msg=msg + ) if self._decode_filter_async_call is not None: - await self._decode_filter_async_call(bound_obj, msg_dict, - msg_decoded) + await self._decode_filter_async_call( + bound_obj, msg_dict, msg_decoded + ) return msg_decoded - def encode_user_response(self, bound_obj: Any, message: Message, - response: Response | None) -> str: + def encode_user_response( + self, bound_obj: Any, message: Message, response: Response | None + ) -> str: """Encode a response provided by the user for sending.""" assert isinstance(response, Response | None) # (user should never explicitly return error-responses) - assert (response is None - or type(response) in message.get_response_types()) + assert ( + response is None or type(response) in message.get_response_types() + ) # A return value of None equals EmptySysResponse. out_response: Response | SysResponse @@ -255,24 +287,26 @@ class MessageReceiver: response_dict = self.protocol.response_to_dict(out_response) if self._encode_filter_call is not None: - self._encode_filter_call(bound_obj, message, out_response, - response_dict) + self._encode_filter_call( + bound_obj, message, out_response, response_dict + ) return self.protocol.encode_dict(response_dict) - def encode_error_response(self, bound_obj: Any, message: Message | None, - exc: Exception) -> str: - """Given an error, return a response ready for sending.""" - response = self.protocol.error_to_response(exc) + def encode_error_response( + self, bound_obj: Any, message: Message | None, exc: Exception + ) -> tuple[str, bool]: + """Given an error, return sysresponse str and whether to log.""" + response, dolog = self.protocol.error_to_response(exc) response_dict = self.protocol.response_to_dict(response) if self._encode_filter_call is not None: - self._encode_filter_call(bound_obj, message, response, - response_dict) - return self.protocol.encode_dict(response_dict) + self._encode_filter_call( + bound_obj, message, response, response_dict + ) + return self.protocol.encode_dict(response_dict), dolog - def handle_raw_message(self, - bound_obj: Any, - msg: str, - raise_unregistered: bool = False) -> str: + def handle_raw_message( + self, bound_obj: Any, msg: str, raise_unregistered: bool = False + ) -> str: """Decode, handle, and return an response for a message. if 'raise_unregistered' is True, will raise an @@ -293,16 +327,20 @@ class MessageReceiver: return self.encode_user_response(bound_obj, msg_decoded, response) except Exception as exc: - if (raise_unregistered - and isinstance(exc, UnregisteredMessageIDError)): + if raise_unregistered and isinstance( + exc, UnregisteredMessageIDError + ): raise - return self.encode_error_response(bound_obj, msg_decoded, exc) + rstr, dolog = self.encode_error_response( + bound_obj, msg_decoded, exc + ) + if dolog: + logging.exception('Error in efro.message handling.') + return rstr async def handle_raw_message_async( - self, - bound_obj: Any, - msg: str, - raise_unregistered: bool = False) -> str: + self, bound_obj: Any, msg: str, raise_unregistered: bool = False + ) -> str: """Should be called when the receiver gets a message. The return value is the raw response to the message. @@ -311,7 +349,8 @@ class MessageReceiver: msg_decoded: Message | None = None try: msg_decoded = await self._decode_incoming_message_async( - bound_obj, msg) + bound_obj, msg + ) msgtype = type(msg_decoded) handler = self._handlers.get(msgtype) if handler is None: @@ -321,10 +360,16 @@ class MessageReceiver: return self.encode_user_response(bound_obj, msg_decoded, response) except Exception as exc: - if (raise_unregistered - and isinstance(exc, UnregisteredMessageIDError)): + if raise_unregistered and isinstance( + exc, UnregisteredMessageIDError + ): raise - return self.encode_error_response(bound_obj, msg_decoded, exc) + rstr, dolog = self.encode_error_response( + bound_obj, msg_decoded, exc + ) + if dolog: + logging.exception('Error in efro.message handling.') + return rstr class BoundMessageReceiver: @@ -348,9 +393,9 @@ class BoundMessageReceiver: """Given an error, return a response ready to send. This should be used for any errors that happen outside of - of standard handle_raw_message calls. Any errors within those - calls should be automatically returned as encoded strings. + standard handle_raw_message calls. Any errors within those + calls will be automatically returned as encoded strings. """ # Passing None for Message here; we would only have that available # for things going wrong in the handler (which this is not for). - return self._receiver.encode_error_response(self._obj, None, exc) + return self._receiver.encode_error_response(self._obj, None, exc)[0] diff --git a/dist/ba_data/python/efro/message/_sender.py b/dist/ba_data/python/efro/message/_sender.py index 47e1dc5..bd9e94d 100644 --- a/dist/ba_data/python/efro/message/_sender.py +++ b/dist/ba_data/python/efro/message/_sender.py @@ -43,16 +43,19 @@ class MessageSender: self.protocol = protocol self._send_raw_message_call: Callable[[Any, str], str] | None = None self._send_async_raw_message_call: Callable[ - [Any, str], Awaitable[str]] | None = None - self._encode_filter_call: Callable[[Any, Message, dict], - None] | None = None + [Any, str], Awaitable[str] + ] | None = None + self._encode_filter_call: Callable[ + [Any, Message, dict], None + ] | None = None self._decode_filter_call: Callable[ - [Any, Message, dict, Response | SysResponse], None] | None = None + [Any, Message, dict, Response | SysResponse], None + ] | None = None self._peer_desc_call: Callable[[Any], str] | None = None def send_method( - self, call: Callable[[Any, str], - str]) -> Callable[[Any, str], str]: + self, call: Callable[[Any, str], str] + ) -> Callable[[Any, str], str]: """Function decorator for setting raw send method. Send methods take strings and should return strings. @@ -91,8 +94,7 @@ class MessageSender: return call def decode_filter_method( - self, call: Callable[[Any, Message, dict, Response | SysResponse], - None] + self, call: Callable[[Any, Message, dict, Response | SysResponse], None] ) -> Callable[[Any, Message, dict, Response], None]: """Function decorator for defining a decode filter. @@ -103,8 +105,9 @@ class MessageSender: self._decode_filter_call = call return call - def peer_desc_method(self, call: Callable[[Any], - str]) -> Callable[[Any], str]: + def peer_desc_method( + self, call: Callable[[Any], str] + ) -> Callable[[Any], str]: """Function decorator for defining peer descriptions. These are included in error messages or other diagnostics. @@ -115,29 +118,31 @@ class MessageSender: def send(self, bound_obj: Any, message: Message) -> Response | None: """Send a message synchronously.""" - return self.send_split_part_2( + return self.unpack_raw_response( bound_obj=bound_obj, message=message, - raw_response=self.send_split_part_1( + raw_response=self.fetch_raw_response( bound_obj=bound_obj, message=message, ), ) - async def send_async(self, bound_obj: Any, - message: Message) -> Response | None: + async def send_async( + self, bound_obj: Any, message: Message + ) -> Response | None: """Send a message asynchronously.""" - return self.send_split_part_2( + return self.unpack_raw_response( bound_obj=bound_obj, message=message, - raw_response=await self.send_split_part_1_async( + raw_response=await self.fetch_raw_response_async( bound_obj=bound_obj, message=message, ), ) - def send_split_part_1(self, bound_obj: Any, - message: Message) -> Response | SysResponse: + def fetch_raw_response( + self, bound_obj: Any, message: Message + ) -> Response | SysResponse: """Send a message synchronously. Generally you can just call send(); these split versions are @@ -150,25 +155,33 @@ class MessageSender: msg_encoded = self._encode_message(bound_obj, message) try: response_encoded = self._send_raw_message_call( - bound_obj, msg_encoded) + bound_obj, msg_encoded + ) except Exception as exc: # Any error in the raw send call gets recorded as either # a local or communication error. return ErrorSysResponse( - error_message= - f'Error in MessageSender @send_method ({type(exc)}): {exc}', - error_type=(ErrorSysResponse.ErrorType.COMMUNICATION - if isinstance(exc, CommunicationError) else - ErrorSysResponse.ErrorType.LOCAL)) + error_message=f'Error in MessageSender @send_method' + f' ({type(exc)}): {exc}', + error_type=( + ErrorSysResponse.ErrorType.COMMUNICATION + if isinstance(exc, CommunicationError) + else ErrorSysResponse.ErrorType.LOCAL + ), + ) return self._decode_raw_response(bound_obj, message, response_encoded) - async def send_split_part_1_async( - self, bound_obj: Any, message: Message) -> Response | SysResponse: - """Send a message asynchronously. + async def fetch_raw_response_async( + self, bound_obj: Any, message: Message + ) -> Response | SysResponse: + """Fetch a raw message response. - Generally you can just call send(); these split versions are - for when message sending and response handling need to happen - in different contexts/threads. + The result of this should be passed to unpack_raw_response() to + produce the final message result. + + Generally you can just call send(); calling fetch and unpack + manually is for when message sending and response handling need + to happen in different contexts/threads. """ if self._send_async_raw_message_call is None: @@ -177,31 +190,39 @@ class MessageSender: msg_encoded = self._encode_message(bound_obj, message) try: response_encoded = await self._send_async_raw_message_call( - bound_obj, msg_encoded) + bound_obj, msg_encoded + ) except Exception as exc: # Any error in the raw send call gets recorded as either # a local or communication error. return ErrorSysResponse( - error_message= - f'Error in MessageSender @send_async_method ({type(exc)}):' - f' {exc}', - error_type=(ErrorSysResponse.ErrorType.COMMUNICATION - if isinstance(exc, CommunicationError) else - ErrorSysResponse.ErrorType.LOCAL)) + error_message=f'Error in MessageSender @send_async_method' + f' ({type(exc)}): {exc}', + error_type=( + ErrorSysResponse.ErrorType.COMMUNICATION + if isinstance(exc, CommunicationError) + else ErrorSysResponse.ErrorType.LOCAL + ), + ) return self._decode_raw_response(bound_obj, message, response_encoded) - def send_split_part_2( - self, bound_obj: Any, message: Message, - raw_response: Response | SysResponse) -> Response | None: - """Complete message sending (both sync and async). + def unpack_raw_response( + self, + bound_obj: Any, + message: Message, + raw_response: Response | SysResponse, + ) -> Response | None: + """Convert a raw fetched response into a final response/error/etc. - Generally you can just call send(); these split versions are - for when message sending and response handling need to happen - in different contexts/threads. + Generally you can just call send(); calling fetch and unpack + manually is for when message sending and response handling need + to happen in different contexts/threads. """ response = self._unpack_raw_response(bound_obj, raw_response) - assert (response is None - or type(response) in type(message).get_response_types()) + assert ( + response is None + or type(response) in type(message).get_response_types() + ) return response def _encode_message(self, bound_obj: Any, message: Message) -> str: @@ -211,8 +232,9 @@ class MessageSender: self._encode_filter_call(bound_obj, message, msg_dict) return self.protocol.encode_dict(msg_dict) - def _decode_raw_response(self, bound_obj: Any, message: Message, - response_encoded: str) -> Response | SysResponse: + def _decode_raw_response( + self, bound_obj: Any, message: Message, response_encoded: str + ) -> Response | SysResponse: """Create a Response from returned data. These Responses may encapsulate things like remote errors and @@ -225,8 +247,9 @@ class MessageSender: response_dict = self.protocol.decode_dict(response_encoded) response = self.protocol.response_from_dict(response_dict) if self._decode_filter_call is not None: - self._decode_filter_call(bound_obj, message, response_dict, - response) + self._decode_filter_call( + bound_obj, message, response_dict, response + ) except Exception: # If we got to this point, we successfully communicated # with the other end so errors represent protocol mismatches @@ -235,14 +258,15 @@ class MessageSender: # available directly to the user later. logging.exception('Error decoding raw response') response = ErrorSysResponse( - error_message= - 'Error decoding raw response; see log for details.', - error_type=ErrorSysResponse.ErrorType.LOCAL) + error_message='Error decoding raw response;' + ' see log for details.', + error_type=ErrorSysResponse.ErrorType.LOCAL, + ) return response def _unpack_raw_response( - self, bound_obj: Any, - raw_response: Response | SysResponse) -> Response | None: + self, bound_obj: Any, raw_response: Response | SysResponse + ) -> Response | None: """Given a raw Response, unpacks to special values or Exceptions. The result of this call is what should be passed to users. @@ -258,8 +282,10 @@ class MessageSender: # Some error occurred. Raise a local Exception for it. if isinstance(raw_response, ErrorSysResponse): - if (raw_response.error_type is - ErrorSysResponse.ErrorType.COMMUNICATION): + if ( + raw_response.error_type + is ErrorSysResponse.ErrorType.COMMUNICATION + ): raise CommunicationError(raw_response.error_message) # If something went wrong on *our* end of the connection, @@ -268,14 +294,29 @@ class MessageSender: raise RuntimeError(raw_response.error_message) # If they want to support clean errors, do those. - if (self.protocol.forward_clean_errors and raw_response.error_type - is ErrorSysResponse.ErrorType.REMOTE_CLEAN): + if ( + self.protocol.forward_clean_errors + and raw_response.error_type + is ErrorSysResponse.ErrorType.REMOTE_CLEAN + ): raise CleanError(raw_response.error_message) + if ( + self.protocol.forward_communication_errors + and raw_response.error_type + is ErrorSysResponse.ErrorType.REMOTE_COMMUNICATION + ): + raise CommunicationError(raw_response.error_message) + # Everything else gets lumped in as a remote error. - raise RemoteError(raw_response.error_message, - peer_desc=('peer' if self._peer_desc_call is None - else self._peer_desc_call(bound_obj))) + raise RemoteError( + raw_response.error_message, + peer_desc=( + 'peer' + if self._peer_desc_call is None + else self._peer_desc_call(bound_obj) + ), + ) assert isinstance(raw_response, Response) return raw_response @@ -311,20 +352,23 @@ class BoundMessageSender: subclasses instead of this; it will provide better type safety. """ assert self._obj is not None - return await self._sender.send_async(bound_obj=self._obj, - message=message) + return await self._sender.send_async( + bound_obj=self._obj, message=message + ) - async def send_split_part_1_async_untyped( - self, message: Message) -> Response | SysResponse: + async def fetch_raw_response_async_untyped( + self, message: Message + ) -> Response | SysResponse: """Split send (part 1 of 2).""" assert self._obj is not None - return await self._sender.send_split_part_1_async(bound_obj=self._obj, - message=message) + return await self._sender.fetch_raw_response_async( + bound_obj=self._obj, message=message + ) - def send_split_part_2_untyped( - self, message: Message, - raw_response: Response | SysResponse) -> Response | None: + def unpack_raw_response_untyped( + self, message: Message, raw_response: Response | SysResponse + ) -> Response | None: """Split send (part 2 of 2).""" - return self._sender.send_split_part_2(bound_obj=self._obj, - message=message, - raw_response=raw_response) + return self._sender.unpack_raw_response( + bound_obj=self._obj, message=message, raw_response=raw_response + ) diff --git a/dist/ba_data/python/efro/rpc.py b/dist/ba_data/python/efro/rpc.py index b7a14d0..c6412bf 100644 --- a/dist/ba_data/python/efro/rpc.py +++ b/dist/ba_data/python/efro/rpc.py @@ -14,10 +14,16 @@ from threading import current_thread from typing import TYPE_CHECKING, Annotated from efro.util import assert_never -from efro.error import (CommunicationError, - is_asyncio_streams_communication_error) -from efro.dataclassio import (dataclass_to_json, dataclass_from_json, - ioprepped, IOAttrs) +from efro.error import ( + CommunicationError, + is_asyncio_streams_communication_error, +) +from efro.dataclassio import ( + dataclass_to_json, + dataclass_from_json, + ioprepped, + IOAttrs, +) if TYPE_CHECKING: from typing import Literal, Awaitable, Callable @@ -62,7 +68,8 @@ OUR_PROTOCOL = 2 def ssl_stream_writer_underlying_transport_info( - writer: asyncio.StreamWriter) -> str: + writer: asyncio.StreamWriter, +) -> str: """For debugging SSL Stream connections; returns raw transport info.""" # Note: accessing internals here so just returning info and not # actual objs to reduce potential for breakage. @@ -80,6 +87,11 @@ 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.. + if bool(True): + return + # Hopefully can remove this in Python 3.11?... # see issue with is_closing() below for more details. transport = getattr(writer, '_transport', None) @@ -120,7 +132,9 @@ class _InFlightMessage: def __init__(self) -> None: self._response: bytes | None = None self._got_response = asyncio.Event() - self.wait_task = asyncio.create_task(self._wait()) + self.wait_task = asyncio.create_task( + self._wait(), name='rpc in flight msg wait' + ) async def _wait(self) -> bytes: await self._got_response.wait() @@ -162,24 +176,26 @@ class RPCEndpoint: # disconnect. DEFAULT_KEEPALIVE_TIMEOUT = 30.0 - def __init__(self, - handle_raw_message_call: Callable[[bytes], Awaitable[bytes]], - reader: asyncio.StreamReader, - writer: asyncio.StreamWriter, - label: str, - debug_print: bool = False, - debug_print_io: bool = False, - debug_print_call: Callable[[str], None] | None = None, - keepalive_interval: float = DEFAULT_KEEPALIVE_INTERVAL, - keepalive_timeout: float = DEFAULT_KEEPALIVE_TIMEOUT) -> None: + def __init__( + self, + handle_raw_message_call: Callable[[bytes], Awaitable[bytes]], + reader: asyncio.StreamReader, + writer: asyncio.StreamWriter, + label: str, + debug_print: bool = False, + debug_print_io: bool = False, + debug_print_call: Callable[[str], None] | None = None, + keepalive_interval: float = DEFAULT_KEEPALIVE_INTERVAL, + keepalive_timeout: float = DEFAULT_KEEPALIVE_TIMEOUT, + ) -> None: self._handle_raw_message_call = handle_raw_message_call self._reader = reader self._writer = writer - self._debug_print = debug_print - self._debug_print_io = debug_print_io + self.debug_print = debug_print + self.debug_print_io = debug_print_io if debug_print_call is None: debug_print_call = print - self._debug_print_call: Callable[[str], None] = debug_print_call + self.debug_print_call: Callable[[str], None] = debug_print_call self._label = label self._thread = current_thread() self._closing = False @@ -194,10 +210,12 @@ class RPCEndpoint: self._did_close_writer = False self._did_wait_closed_writer = False self._did_out_packets_buildup_warning = False + self._total_bytes_read = 0 + self._create_time = time.monotonic() # Need to hold weak-refs to these otherwise it creates dep-loops # which keeps us alive. - self._tasks: list[weakref.ref[asyncio.Task]] = [] + self._tasks: list[asyncio.Task] = [] # When we last got a keepalive or equivalent (time.monotonic value) self._last_keepalive_receive_time: float | None = None @@ -207,23 +225,28 @@ class RPCEndpoint: self._in_flight_messages: dict[int, _InFlightMessage] = {} - if self._debug_print: + if self.debug_print: peername = self._writer.get_extra_info('peername') - self._debug_print_call( - f'{self._label}: connected to {peername} at {self._tm()}.') + self.debug_print_call( + f'{self._label}: connected to {peername} at {self._tm()}.' + ) def __del__(self) -> None: if self._run_called: if not self._did_close_writer: logging.warning( 'RPCEndpoint %d dying with run' - ' called but writer not closed (transport=%s).', id(self), - ssl_stream_writer_underlying_transport_info(self._writer)) + ' called but writer not closed (transport=%s).', + id(self), + ssl_stream_writer_underlying_transport_info(self._writer), + ) elif not self._did_wait_closed_writer: logging.warning( 'RPCEndpoint %d dying with run called' - ' but writer not wait-closed (transport=%s).', id(self), - ssl_stream_writer_underlying_transport_info(self._writer)) + ' but writer not wait-closed (transport=%s).', + id(self), + ssl_stream_writer_underlying_transport_info(self._writer), + ) # Currently seeing rare issue where sockets don't go down; # let's add a timer to force the issue until we can figure it out. @@ -239,8 +262,10 @@ class RPCEndpoint: except asyncio.CancelledError: # We aren't really designed to be cancelled so let's warn # if it happens. - logging.warning('RPCEndpoint.run got CancelledError;' - ' want to try and avoid this.') + logging.warning( + 'RPCEndpoint.run got CancelledError;' + ' want to try and avoid this.' + ) raise async def _do_run(self) -> None: @@ -253,13 +278,19 @@ class RPCEndpoint: core_tasks = [ asyncio.create_task( - self._run_core_task('keepalive', self._run_keepalive_task())), + self._run_core_task('keepalive', self._run_keepalive_task()), + name='rpc keepalive', + ), asyncio.create_task( - self._run_core_task('read', self._run_read_task())), + self._run_core_task('read', self._run_read_task()), + name='rpc read', + ), asyncio.create_task( - self._run_core_task('write', self._run_write_task())) + self._run_core_task('write', self._run_write_task()), + name='rpc write', + ), ] - self._tasks += [weakref.ref(t) for t in core_tasks] + self._tasks += core_tasks # Run our core tasks until they all complete. results = await asyncio.gather(*core_tasks, return_exceptions=True) @@ -270,13 +301,17 @@ class RPCEndpoint: # We want to know if any errors happened aside from CancelledError # (which are BaseExceptions, not Exception). if isinstance(result, Exception): - logging.warning('Got unexpected error from %s core task: %s', - self._label, result) + logging.warning( + 'Got unexpected error from %s core task: %s', + self._label, + result, + ) if not all(task.done() for task in core_tasks): logging.warning( 'RPCEndpoint %d: not all core tasks marked done after gather.', - id(self)) + id(self), + ) # Shut ourself down. try: @@ -285,22 +320,43 @@ class RPCEndpoint: except Exception: logging.exception('Error closing %s.', self._label) - if self._debug_print: - self._debug_print_call(f'{self._label}: finished.') + if self.debug_print: + self.debug_print_call(f'{self._label}: finished.') - async def send_message(self, - message: bytes, - timeout: float | None = None) -> bytes: + async def send_message( + self, + message: bytes, + timeout: float | None = None, + close_on_error: bool = True, + ) -> bytes: """Send a message to the peer and return a response. If timeout is not provided, the default will be used. Raises a CommunicationError if the round trip is not completed for any reason. + + By default, the entire endpoint will go down in the case of + errors. This allows messages to be treated as 'reliable' with + respect to a given endpoint. Pass close_on_error=False to + override this for a particular message. """ + # pylint: disable=too-many-branches + + if self.debug_print_io: + self.debug_print_call( + f'{self._label}: sending message of size {len(message)}' + f' at {self._tm()}.' + ) + self._check_env() if self._closing: - raise CommunicationError('Endpoint is closed') + raise CommunicationError('Endpoint is closed.') + + if self.debug_print_io: + self.debug_print_call( + f'{self._label}: have peerinfo? {self._peer_info is not None}.' + ) # We need to know their protocol, so if we haven't gotten a handshake # from them yet, just wait. @@ -316,6 +372,11 @@ class RPCEndpoint: message_id = self._next_message_id self._next_message_id = (self._next_message_id + 1) % 65536 + if self.debug_print_io: + self.debug_print_call( + f'{self._label}: will enqueue at {self._tm()}.' + ) + # FIXME - should handle backpressure (waiting here if there are # enough packets already enqueued). @@ -323,16 +384,26 @@ class RPCEndpoint: # Payload consists of type (1b), message_id (2b), # len (4b), and data. self._enqueue_outgoing_packet( - _PacketType.MESSAGE_BIG.value.to_bytes(1, _BYTE_ORDER) + - message_id.to_bytes(2, _BYTE_ORDER) + - len(message).to_bytes(4, _BYTE_ORDER) + message) + _PacketType.MESSAGE_BIG.value.to_bytes(1, _BYTE_ORDER) + + message_id.to_bytes(2, _BYTE_ORDER) + + len(message).to_bytes(4, _BYTE_ORDER) + + message + ) else: # Payload consists of type (1b), message_id (2b), # len (2b), and data. self._enqueue_outgoing_packet( - _PacketType.MESSAGE.value.to_bytes(1, _BYTE_ORDER) + - message_id.to_bytes(2, _BYTE_ORDER) + - len(message).to_bytes(2, _BYTE_ORDER) + message) + _PacketType.MESSAGE.value.to_bytes(1, _BYTE_ORDER) + + message_id.to_bytes(2, _BYTE_ORDER) + + len(message).to_bytes(2, _BYTE_ORDER) + + message + ) + + if self.debug_print_io: + self.debug_print_call( + f'{self._label}: enqueued message of size {len(message)}' + f' at {self._tm()}.' + ) # Make an entry so we know this message is out there. assert message_id not in self._in_flight_messages @@ -340,7 +411,7 @@ class RPCEndpoint: # Also add its task to our list so we properly cancel it if we die. self._prune_tasks() # Keep our list from filling with dead tasks. - self._tasks.append(weakref.ref(msgobj.wait_task)) + self._tasks.append(msgobj.wait_task) # Note: we always want to incorporate a timeout. Individual # messages may hang or error on the other end and this ensures @@ -353,25 +424,44 @@ class RPCEndpoint: return await asyncio.wait_for(msgobj.wait_task, timeout=timeout) except asyncio.CancelledError as exc: # Question: we assume this means the above wait_for() was - # cancelled; what happens if a task running *us* is cancelled - # though? - if self._debug_print: - self._debug_print_call( - f'{self._label}: message {message_id} was cancelled.') + # cancelled; how do we distinguish between this and *us* being + # cancelled though? + if self.debug_print: + self.debug_print_call( + f'{self._label}: message {message_id} was cancelled.' + ) + if close_on_error: + self.close() + raise CommunicationError() from exc - except asyncio.TimeoutError as exc: - if self._debug_print: - self._debug_print_call( - f'{self._label}: message {message_id} timed out.') + except Exception as exc: - # Stop waiting on the response. - msgobj.wait_task.cancel() + # If our timer timed-out or anything else went wrong with + # the stream, lump it in as a communication error. + if isinstance( + exc, asyncio.TimeoutError + ) or is_asyncio_streams_communication_error(exc): - # Remove the record of this message. - del self._in_flight_messages[message_id] + if self.debug_print: + self.debug_print_call( + f'{self._label}: got {type(exc)} sending message' + f' {message_id}; raising CommunicationError.' + ) - # Let the user know something went wrong. - raise CommunicationError() from exc + # Stop waiting on the response. + msgobj.wait_task.cancel() + + # Remove the record of this message. + del self._in_flight_messages[message_id] + + if close_on_error: + self.close() + + # Let the user know something went wrong. + raise CommunicationError() from exc + + # Some unexpected error; let it bubble up. + raise def close(self) -> None: """I said seagulls; mmmm; stop it now.""" @@ -380,21 +470,21 @@ class RPCEndpoint: if self._closing: return - if self._debug_print: - self._debug_print_call(f'{self._label}: closing...') + if self.debug_print: + self.debug_print_call(f'{self._label}: closing...') self._closing = True # Kill all of our in-flight tasks. - if self._debug_print: - self._debug_print_call(f'{self._label}: cancelling tasks...') + if self.debug_print: + self.debug_print_call(f'{self._label}: cancelling tasks...') for task in self._get_live_tasks(): task.cancel() # Close our writer. assert not self._did_close_writer - if self._debug_print: - self._debug_print_call(f'{self._label}: closing writer...') + if self.debug_print: + self.debug_print_call(f'{self._label}: closing writer...') self._writer.close() self._did_close_writer = True @@ -407,7 +497,11 @@ class RPCEndpoint: return self._closing async def wait_closed(self) -> None: - """I said seagulls; mmmm; stop it now.""" + """I said seagulls; mmmm; stop it now. + + Wait for the endpoint to finish closing. This is called by run() + so generally does not need to be explicitly called. + """ # pylint: disable=too-many-branches self._check_env() @@ -420,14 +514,22 @@ class RPCEndpoint: raise RuntimeError('Must be called after close()') if not self._did_close_writer: - logging.warning('RPCEndpoint wait_closed() called but never' - ' explicitly closed writer.') + logging.warning( + 'RPCEndpoint wait_closed() called but never' + ' explicitly closed writer.' + ) live_tasks = self._get_live_tasks() - if self._debug_print: - self._debug_print_call( + + # Don't need our task list anymore; this should + # break any cyclical refs from tasks referring to us. + self._tasks = [] + + if self.debug_print: + self.debug_print_call( f'{self._label}: waiting for tasks to finish: ' - f' ({live_tasks=})...') + f' ({live_tasks=})...' + ) # Wait for all of our in-flight tasks to wrap up. results = await asyncio.gather(*live_tasks, return_exceptions=True) @@ -435,17 +537,22 @@ class RPCEndpoint: # We want to know if any errors happened aside from CancelledError # (which are BaseExceptions, not Exception). if isinstance(result, Exception): - logging.warning('Got unexpected error cleaning up %s task: %s', - self._label, result) + logging.warning( + 'Got unexpected error cleaning up %s task: %s', + self._label, + result, + ) if not all(task.done() for task in live_tasks): logging.warning( 'RPCEndpoint %d: not all live tasks marked done after gather.', - id(self)) + id(self), + ) - if self._debug_print: - self._debug_print_call( - f'{self._label}: tasks finished; waiting for writer close...') + if self.debug_print: + self.debug_print_call( + f'{self._label}: tasks finished; waiting for writer close...' + ) # Now wait for our writer to finish going down. # When we close our writer it generally triggers errors @@ -459,34 +566,43 @@ class RPCEndpoint: # indefinitely. See https://github.com/python/cpython/issues/83939 # It sounds like this should be fixed in 3.11 but for now just # forcing the issue with a timeout here. - await asyncio.wait_for(self._writer.wait_closed(), timeout=30.0) + await asyncio.wait_for( + self._writer.wait_closed(), + # timeout=60.0 * 6.0, + timeout=30.0, + ) except asyncio.TimeoutError: logging.info( 'Timeout on _writer.wait_closed() for %s rpc (transport=%s).', self._label, - ssl_stream_writer_underlying_transport_info(self._writer)) - if self._debug_print: - self._debug_print_call( + ssl_stream_writer_underlying_transport_info(self._writer), + ) + if self.debug_print: + self.debug_print_call( f'{self._label}: got timeout in _writer.wait_closed();' - ' This should be fixed in future Python versions.') + ' This should be fixed in future Python versions.' + ) except Exception as exc: if not self._is_expected_connection_error(exc): logging.exception('Error closing _writer for %s.', self._label) else: - if self._debug_print: - self._debug_print_call( + if self.debug_print: + self.debug_print_call( f'{self._label}: silently ignoring error in' - f' _writer.wait_closed(): {exc}.') + f' _writer.wait_closed(): {exc}.' + ) except asyncio.CancelledError: - logging.warning('RPCEndpoint.wait_closed()' - ' got asyncio.CancelledError; not expected.') + logging.warning( + 'RPCEndpoint.wait_closed()' + ' got asyncio.CancelledError; not expected.' + ) raise assert not self._did_wait_closed_writer self._did_wait_closed_writer = True def _tm(self) -> str: """Simple readable time value for debugging.""" - tval = time.time() % 100.0 + tval = time.monotonic() % 100.0 return f'{tval:.2f}' async def _run_read_task(self) -> None: @@ -494,15 +610,21 @@ class RPCEndpoint: self._check_env() assert self._peer_info is None + # Bug fix: if we don't have this set we will never time out + # if we never receive any data from the other end. + self._last_keepalive_receive_time = time.monotonic() + # The first thing they should send us is their handshake; then # we'll know if/how we can talk to them. mlen = await self._read_int_32() - message = (await self._reader.readexactly(mlen)) + message = await self._reader.readexactly(mlen) + self._total_bytes_read += mlen self._peer_info = dataclass_from_json(_PeerInfo, message.decode()) self._last_keepalive_receive_time = time.monotonic() - if self._debug_print: - self._debug_print_call( - f'{self._label}: received handshake at {self._tm()}.') + if self.debug_print: + self.debug_print_call( + f'{self._label}: received handshake at {self._tm()}.' + ) # Now just sit and handle stuff as it comes in. while True: @@ -514,9 +636,11 @@ class RPCEndpoint: raise RuntimeError('Got multiple handshakes') if mtype is _PacketType.KEEPALIVE: - if self._debug_print_io: - self._debug_print_call(f'{self._label}: received keepalive' - f' at {self._tm()}.') + if self.debug_print_io: + self.debug_print_call( + f'{self._label}: received keepalive' + f' at {self._tm()}.' + ) self._last_keepalive_receive_time = time.monotonic() elif mtype is _PacketType.MESSAGE: @@ -542,21 +666,27 @@ class RPCEndpoint: else: msglen = await self._read_int_16() msg = await self._reader.readexactly(msglen) - if self._debug_print_io: - self._debug_print_call(f'{self._label}: received message {msgid}' - f' of size {msglen} at {self._tm()}.') + self._total_bytes_read += msglen + if self.debug_print_io: + self.debug_print_call( + f'{self._label}: received message {msgid}' + f' of size {msglen} at {self._tm()}.' + ) # Create a message-task to handle this message and return # a response (we don't want to block while that happens). assert not self._closing self._prune_tasks() # Keep from filling with dead tasks. self._tasks.append( - weakref.ref( - asyncio.create_task( - self._handle_raw_message(message_id=msgid, message=msg)))) - if self._debug_print: - self._debug_print_call( - f'{self._label}: done handling message at {self._tm()}.') + asyncio.create_task( + self._handle_raw_message(message_id=msgid, message=msg), + name='efro rpc message handle', + ) + ) + if self.debug_print: + self.debug_print_call( + f'{self._label}: done handling message at {self._tm()}.' + ) async def _handle_response_packet(self, big: bool) -> None: assert self._peer_info is not None @@ -566,19 +696,23 @@ class RPCEndpoint: rsplen = await self._read_int_32() else: rsplen = await self._read_int_16() - if self._debug_print_io: - self._debug_print_call(f'{self._label}: received response {msgid}' - f' of size {rsplen} at {self._tm()}.') + if self.debug_print_io: + self.debug_print_call( + f'{self._label}: received response {msgid}' + f' of size {rsplen} at {self._tm()}.' + ) rsp = await self._reader.readexactly(rsplen) + self._total_bytes_read += rsplen msgobj = self._in_flight_messages.get(msgid) if msgobj is None: # It's possible for us to get a response to a message # that has timed out. In this case we will have no local # record of it. - if self._debug_print: - self._debug_print_call( + if self.debug_print: + self.debug_print_call( f'{self._label}: got response for nonexistent' - f' message id {msgid}; perhaps it timed out?') + f' message id {msgid}; perhaps it timed out?' + ) else: msgobj.set_response(rsp) @@ -589,8 +723,11 @@ class RPCEndpoint: # Introduce ourself so our peer knows how it can talk to us. data = dataclass_to_json( - _PeerInfo(protocol=OUR_PROTOCOL, - keepalive_interval=self._keepalive_interval)).encode() + _PeerInfo( + protocol=OUR_PROTOCOL, + keepalive_interval=self._keepalive_interval, + ) + ).encode() self._writer.write(len(data).to_bytes(4, _BYTE_ORDER) + data) # Now just write out-messages as they come in. @@ -620,7 +757,9 @@ class RPCEndpoint: if not self._did_out_packets_buildup_warning: logging.warning( '_out_packets building up too' - ' much on RPCEndpoint %s.', id(self)) + ' much on RPCEndpoint %s.', + id(self), + ) self._did_out_packets_buildup_warning = True async def _run_keepalive_task(self) -> None: @@ -639,21 +778,25 @@ class RPCEndpoint: await asyncio.sleep(self._keepalive_interval) if not self.test_suppress_keepalives: self._enqueue_outgoing_packet( - _PacketType.KEEPALIVE.value.to_bytes(1, _BYTE_ORDER)) + _PacketType.KEEPALIVE.value.to_bytes(1, _BYTE_ORDER) + ) # Also go ahead and handle dropping the connection if we # haven't heard from the peer in a while. # NOTE: perhaps we want to do something more exact than # this which only checks once per keepalive-interval?.. now = time.monotonic() - if (self._last_keepalive_receive_time is not None - and now - self._last_keepalive_receive_time > - self._keepalive_timeout): - if self._debug_print: + if ( + self._last_keepalive_receive_time is not None + and now - self._last_keepalive_receive_time + > self._keepalive_timeout + ): + if self.debug_print: since = now - self._last_keepalive_receive_time - self._debug_print_call( + self.debug_print_call( f'{self._label}: reached keepalive time-out' - f' ({since:.1f}s).') + f' ({since:.1f}s).' + ) raise _KeepaliveTimeoutError() async def _run_core_task(self, tasklabel: str, call: Awaitable) -> None: @@ -663,22 +806,31 @@ class RPCEndpoint: # We expect connection errors to put us here, but make noise # if something else does. if not self._is_expected_connection_error(exc): - logging.exception('Unexpected error in rpc %s %s task.', - self._label, tasklabel) + logging.exception( + 'Unexpected error in rpc %s %s task' + ' (age=%.1f, total_bytes_read=%d).', + self._label, + tasklabel, + time.monotonic() - self._create_time, + self._total_bytes_read, + ) else: - if self._debug_print: - self._debug_print_call( + if self.debug_print: + self.debug_print_call( f'{self._label}: {tasklabel} task will exit cleanly' - f' due to {exc!r}.') + f' due to {exc!r}.' + ) finally: # Any core task exiting triggers shutdown. - if self._debug_print: - self._debug_print_call( - f'{self._label}: {tasklabel} task exiting...') + if self.debug_print: + self.debug_print_call( + f'{self._label}: {tasklabel} task exiting...' + ) self.close() - async def _handle_raw_message(self, message_id: int, - message: bytes) -> None: + async def _handle_raw_message( + self, message_id: int, message: bytes + ) -> None: try: response = await self._handle_raw_message_call(message) except Exception: @@ -693,30 +845,39 @@ class RPCEndpoint: if self._peer_info.protocol == 1: if len(response) > 65535: - raise RuntimeError( - 'Response cannot be larger than 65535 bytes') + raise RuntimeError('Response cannot be larger than 65535 bytes') # Now send back our response. # Payload consists of type (1b), msgid (2b), len (2b), and data. if len(response) > 65535: self._enqueue_outgoing_packet( - _PacketType.RESPONSE_BIG.value.to_bytes(1, _BYTE_ORDER) + - message_id.to_bytes(2, _BYTE_ORDER) + - len(response).to_bytes(4, _BYTE_ORDER) + response) + _PacketType.RESPONSE_BIG.value.to_bytes(1, _BYTE_ORDER) + + message_id.to_bytes(2, _BYTE_ORDER) + + len(response).to_bytes(4, _BYTE_ORDER) + + response + ) else: self._enqueue_outgoing_packet( - _PacketType.RESPONSE.value.to_bytes(1, _BYTE_ORDER) + - message_id.to_bytes(2, _BYTE_ORDER) + - len(response).to_bytes(2, _BYTE_ORDER) + response) + _PacketType.RESPONSE.value.to_bytes(1, _BYTE_ORDER) + + message_id.to_bytes(2, _BYTE_ORDER) + + len(response).to_bytes(2, _BYTE_ORDER) + + response + ) async def _read_int_8(self) -> int: - return int.from_bytes(await self._reader.readexactly(1), _BYTE_ORDER) + out = int.from_bytes(await self._reader.readexactly(1), _BYTE_ORDER) + self._total_bytes_read += 1 + return out async def _read_int_16(self) -> int: - return int.from_bytes(await self._reader.readexactly(2), _BYTE_ORDER) + out = int.from_bytes(await self._reader.readexactly(2), _BYTE_ORDER) + self._total_bytes_read += 2 + return out async def _read_int_32(self) -> int: - return int.from_bytes(await self._reader.readexactly(4), _BYTE_ORDER) + out = int.from_bytes(await self._reader.readexactly(4), _BYTE_ORDER) + self._total_bytes_read += 4 + return out @classmethod def _is_expected_connection_error(cls, exc: Exception) -> bool: @@ -729,11 +890,14 @@ class RPCEndpoint: def _check_env(self) -> None: # I was seeing that asyncio stuff wasn't working as expected if - # created in one thread and used in another, so let's enforce - # a single thread for all use of an instance. + # created in one thread and used in another (and have verified + # that this is part of the design), so let's enforce a single + # thread for all use of an instance. if current_thread() is not self._thread: - raise RuntimeError('This must be called from the same thread' - ' that the endpoint was created in.') + raise RuntimeError( + 'This must be called from the same thread' + ' that the endpoint was created in.' + ) # This should always be the case if thread is the same. assert asyncio.get_running_loop() is self._event_loop @@ -742,26 +906,18 @@ class RPCEndpoint: """Enqueue a raw packet to be sent. Must be called from our loop.""" self._check_env() - if self._debug_print_io: - self._debug_print_call(f'{self._label}: enqueueing outgoing packet' - f' {data[:50]!r} at {self._tm()}.') + if self.debug_print_io: + self.debug_print_call( + f'{self._label}: enqueueing outgoing packet' + f' {data[:50]!r} at {self._tm()}.' + ) # Add the data and let our write task know about it. self._out_packets.append(data) self._have_out_packets.set() def _prune_tasks(self) -> None: - out: list[weakref.ref[asyncio.Task]] = [] - for task_weak_ref in self._tasks: - task = task_weak_ref() - if task is not None and not task.done(): - out.append(task_weak_ref) - self._tasks = out + self._tasks = self._get_live_tasks() def _get_live_tasks(self) -> list[asyncio.Task]: - out: list[asyncio.Task] = [] - for task_weak_ref in self._tasks: - task = task_weak_ref() - if task is not None and not task.done(): - out.append(task) - return out + return [t for t in self._tasks if not t.done()] diff --git a/dist/ba_data/python/efro/terminal.py b/dist/ba_data/python/efro/terminal.py index d0351d5..38ec78e 100644 --- a/dist/ba_data/python/efro/terminal.py +++ b/dist/ba_data/python/efro/terminal.py @@ -95,6 +95,7 @@ def _windows_enable_color() -> bool: import msvcrt import ctypes from ctypes import wintypes + kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) # type: ignore ERROR_INVALID_PARAMETER = 0x0057 @@ -111,7 +112,7 @@ def _windows_enable_color() -> bool: kernel32.SetConsoleMode.errcheck = _check_bool kernel32.SetConsoleMode.argtypes = (wintypes.HANDLE, wintypes.DWORD) - def set_conout_mode(new_mode: int, mask: int = 0xffffffff) -> int: + def set_conout_mode(new_mode: int, mask: int = 0xFFFFFFFF) -> int: # don't assume StandardOutput is a console. # open CONOUT$ instead fdout = os.open('CONOUT$', os.O_RDWR) @@ -146,6 +147,7 @@ def _windows_enable_color() -> bool: class ClrBase: """Base class for color convenience class.""" + RST: ClassVar[str] BLD: ClassVar[str] UND: ClassVar[str] @@ -198,6 +200,7 @@ class ClrAlways(ClrBase): This version has colors always enabled. Generally you should use Clr which points to the correct enabled/disabled class depending on the environment. """ + color_enabled = True # Styles @@ -253,6 +256,7 @@ class ClrNever(ClrBase): This version has colors disabled. Generally you should use Clr which points to the correct enabled/disabled class depending on the environment. """ + color_enabled = False # Styles @@ -303,8 +307,13 @@ class ClrNever(ClrBase): _envval = os.environ.get('EFRO_TERMCOLORS') -_color_enabled: bool = (True if _envval == '1' else - False if _envval == '0' else _default_color_enabled()) +_color_enabled: bool = ( + True + if _envval == '1' + else False + if _envval == '0' + else _default_color_enabled() +) Clr: type[ClrBase] if _color_enabled: Clr = ClrAlways diff --git a/dist/ba_data/python/efro/util.py b/dist/ba_data/python/efro/util.py index c8b8df0..699fb06 100644 --- a/dist/ba_data/python/efro/util.py +++ b/dist/ba_data/python/efro/util.py @@ -17,6 +17,7 @@ _pytz_utc: Any # We don't *require* pytz, but we want to support it for tzinfos if available. try: import pytz + _pytz_utc = pytz.utc except ModuleNotFoundError: _pytz_utc = None # pylint: disable=invalid-name @@ -68,16 +69,20 @@ def enum_by_value(cls: type[EnumT], value: Any) -> EnumT: return out except KeyError: # pylint: disable=consider-using-f-string - raise ValueError('%r is not a valid %s' % - (value, cls.__name__)) from None + raise ValueError( + '%r is not a valid %s' % (value, cls.__name__) + ) from None def check_utc(value: datetime.datetime) -> None: """Ensure a datetime value is timezone-aware utc.""" - if (value.tzinfo is not datetime.timezone.utc - and (_pytz_utc is None or value.tzinfo is not _pytz_utc)): - raise ValueError('datetime value does not have timezone set as' - ' datetime.timezone.utc') + if value.tzinfo is not datetime.timezone.utc and ( + _pytz_utc is None or value.tzinfo is not _pytz_utc + ): + raise ValueError( + 'datetime value does not have timezone set as' + ' datetime.timezone.utc' + ) def utc_now() -> datetime.datetime: @@ -94,31 +99,34 @@ def utc_now() -> datetime.datetime: def utc_today() -> datetime.datetime: """Get offset-aware midnight in the utc time zone.""" now = datetime.datetime.now(datetime.timezone.utc) - return datetime.datetime(year=now.year, - month=now.month, - day=now.day, - tzinfo=now.tzinfo) + return datetime.datetime( + year=now.year, month=now.month, day=now.day, tzinfo=now.tzinfo + ) def utc_this_hour() -> datetime.datetime: """Get offset-aware beginning of the current hour in the utc time zone.""" now = datetime.datetime.now(datetime.timezone.utc) - return datetime.datetime(year=now.year, - month=now.month, - day=now.day, - hour=now.hour, - tzinfo=now.tzinfo) + return datetime.datetime( + year=now.year, + month=now.month, + day=now.day, + hour=now.hour, + tzinfo=now.tzinfo, + ) def utc_this_minute() -> datetime.datetime: """Get offset-aware beginning of current minute in the utc time zone.""" now = datetime.datetime.now(datetime.timezone.utc) - return datetime.datetime(year=now.year, - month=now.month, - day=now.day, - hour=now.hour, - minute=now.minute, - tzinfo=now.tzinfo) + return datetime.datetime( + year=now.year, + month=now.month, + day=now.day, + hour=now.hour, + minute=now.minute, + tzinfo=now.tzinfo, + ) def empty_weakref(objtype: type[T]) -> weakref.ref[T]: @@ -172,26 +180,31 @@ class DirtyBit: when updates fail) """ - def __init__(self, - dirty: bool = False, - retry_interval: float = 5.0, - use_lock: bool = False, - auto_dirty_seconds: float | None = None, - min_update_interval: float | None = None): + def __init__( + self, + dirty: bool = False, + retry_interval: float = 5.0, + use_lock: bool = False, + auto_dirty_seconds: float | None = None, + min_update_interval: float | None = None, + ): curtime = time.time() self._retry_interval = retry_interval self._auto_dirty_seconds = auto_dirty_seconds self._min_update_interval = min_update_interval self._dirty = dirty - self._next_update_time: float | None = (curtime if dirty else None) + self._next_update_time: float | None = curtime if dirty else None self._last_update_time: float | None = None self._next_auto_dirty_time: float | None = ( - (curtime + self._auto_dirty_seconds) if - (not dirty and self._auto_dirty_seconds is not None) else None) + (curtime + self._auto_dirty_seconds) + if (not dirty and self._auto_dirty_seconds is not None) + else None + ) self._use_lock = use_lock self.lock: asyncio.Lock if self._use_lock: import asyncio + self.lock = asyncio.Lock() @property @@ -216,11 +229,14 @@ class DirtyBit: # If they want to enforce a minimum update interval, # push out the next update time if it hasn't been long enough. - if (self._min_update_interval is not None - and self._last_update_time is not None): + if ( + self._min_update_interval is not None + and self._last_update_time is not None + ): self._next_update_time = max( self._next_update_time, - self._last_update_time + self._min_update_interval) + self._last_update_time + self._min_update_interval, + ) self._dirty = value @@ -235,8 +251,10 @@ class DirtyBit: curtime = time.time() # Auto-dirty ourself if we're into that. - if (self._next_auto_dirty_time is not None - and curtime > self._next_auto_dirty_time): + if ( + self._next_auto_dirty_time is not None + and curtime > self._next_auto_dirty_time + ): self.dirty = True self._next_auto_dirty_time = None if not self._dirty: @@ -259,7 +277,8 @@ class DispatchMethodWrapper(Generic[ArgT, RetT]): @staticmethod def register( - func: Callable[[Any, Any], RetT]) -> Callable[[Any, Any], RetT]: + func: Callable[[Any, Any], RetT] + ) -> Callable[[Any, Any], RetT]: """Register a new dispatch handler for this dispatch-method.""" registry: dict[Any, Callable] @@ -267,8 +286,8 @@ class DispatchMethodWrapper(Generic[ArgT, RetT]): # noinspection PyProtectedMember,PyTypeHints def dispatchmethod( - func: Callable[[Any, ArgT], - RetT]) -> DispatchMethodWrapper[ArgT, RetT]: + func: Callable[[Any, ArgT], RetT] +) -> DispatchMethodWrapper[ArgT, RetT]: """A variation of functools.singledispatch for methods. Note: as of Python 3.9 there is now functools.singledispatchmethod, @@ -276,6 +295,7 @@ def dispatchmethod( which gives us a reason to keep this one around for now. """ from functools import singledispatch, update_wrapper + origwrapper: Any = singledispatch(func) # Pull this out so hopefully origwrapper can die, @@ -291,8 +311,9 @@ def dispatchmethod( # NOTE: sounds like we can use functools singledispatchmethod in 3.8 def wrapper(*args: Any, **kw: Any) -> Any: if not args or len(args) < 2: - raise TypeError(f'{funcname} requires at least ' - '2 positional arguments') + raise TypeError( + f'{funcname} requires at least ' '2 positional arguments' + ) return dispatch(args[1].__class__)(*args, **kw) @@ -331,24 +352,26 @@ class ValueDispatcher(Generic[ValT, RetT]): return handler() return self._base_call(value) - def _add_handler(self, value: ValT, - call: Callable[[], RetT]) -> Callable[[], RetT]: + def _add_handler( + self, value: ValT, call: Callable[[], RetT] + ) -> Callable[[], RetT]: if value in self._handlers: raise RuntimeError(f'Duplicate handlers added for {value}') self._handlers[value] = call return call def register( - self, - value: ValT) -> Callable[[Callable[[], RetT]], Callable[[], RetT]]: + self, value: ValT + ) -> Callable[[Callable[[], RetT]], Callable[[], RetT]]: """Add a handler to the dispatcher.""" from functools import partial + return partial(self._add_handler, value) def valuedispatch1arg( - call: Callable[[ValT, ArgT], - RetT]) -> ValueDispatcher1Arg[ValT, ArgT, RetT]: + call: Callable[[ValT, ArgT], RetT] +) -> ValueDispatcher1Arg[ValT, ArgT, RetT]: """Like valuedispatch but for functions taking an extra argument.""" return ValueDispatcher1Arg(call) @@ -366,8 +389,9 @@ class ValueDispatcher1Arg(Generic[ValT, ArgT, RetT]): return handler(arg) return self._base_call(value, arg) - def _add_handler(self, value: ValT, - call: Callable[[ArgT], RetT]) -> Callable[[ArgT], RetT]: + def _add_handler( + self, value: ValT, call: Callable[[ArgT], RetT] + ) -> Callable[[ArgT], RetT]: if value in self._handlers: raise RuntimeError(f'Duplicate handlers added for {value}') self._handlers[value] = call @@ -378,6 +402,7 @@ class ValueDispatcher1Arg(Generic[ValT, ArgT, RetT]): ) -> Callable[[Callable[[ArgT], RetT]], Callable[[ArgT], RetT]]: """Add a handler to the dispatcher.""" from functools import partial + return partial(self._add_handler, value) @@ -397,8 +422,8 @@ if TYPE_CHECKING: def valuedispatchmethod( - call: Callable[[SelfT, ValT], - RetT]) -> ValueDispatcherMethod[ValT, RetT]: + call: Callable[[SelfT, ValT], RetT] +) -> ValueDispatcherMethod[ValT, RetT]: """Like valuedispatch but works with methods instead of functions.""" # NOTE: It seems that to wrap a method with a decorator and have self @@ -416,6 +441,7 @@ def valuedispatchmethod( def _register(value: ValT) -> Callable[[Callable[[SelfT], RetT]], None]: from functools import partial + return partial(_add_handler, value) def _call_wrapper(self: SelfT, value: ValT) -> RetT: @@ -520,6 +546,7 @@ def warntype(obj: Any, typ: type[T]) -> T: assert isinstance(typ, type), 'only actual types accepted' if not isinstance(obj, typ): import logging + logging.warning('warntype: expected a %s, got a %s', typ, type(obj)) return obj # type: ignore @@ -533,8 +560,10 @@ def warntype_o(obj: Any, typ: type[T]) -> T | None: assert isinstance(typ, type), 'only actual types accepted' if not isinstance(obj, (typ, type(None))): import logging - logging.warning('warntype: expected a %s or None, got a %s', typ, - type(obj)) + + logging.warning( + 'warntype: expected a %s or None, got a %s', typ, type(obj) + ) return obj # type: ignore @@ -633,8 +662,8 @@ def compact_id(num: int) -> str: Sort order for these ids is the same as the original numbers. """ return _compact_id( - num, '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' - 'abcdefghijklmnopqrstuvwxyz') + num, '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' + ) # NOTE: Even though this is available as part of typing_extensions, keeping @@ -660,15 +689,22 @@ def unchanging_hostname() -> str: # On Mac, this should give the computer name assigned in System Prefs. if platform.system() == 'Darwin': - return subprocess.run( - ['scutil', '--get', 'ComputerName'], - check=True, - capture_output=True).stdout.decode().strip().replace(' ', '-') + return ( + subprocess.run( + ['scutil', '--get', 'ComputerName'], + check=True, + capture_output=True, + ) + .stdout.decode() + .strip() + .replace(' ', '-') + ) return os.uname().nodename -def set_canonical_module(module_globals: dict[str, Any], - names: list[str]) -> None: +def set_canonical_module( + module_globals: dict[str, Any], names: list[str] +) -> None: """Override any __module__ attrs on passed classes/etc. This allows classes to present themselves using clean paths such as @@ -686,7 +722,12 @@ def set_canonical_module(module_globals: dict[str, Any], obj.__module__ = modulename except Exception: import logging + logging.warning( 'set_canonical_module: unable to change __module__' - " from '%s' to '%s' on %s object at '%s'.", existing, - modulename, type(obj), name) + " from '%s' to '%s' on %s object at '%s'.", + existing, + modulename, + type(obj), + name, + ) diff --git a/dist/ba_root/.bsac2 b/dist/ba_root/.bsac2 new file mode 100644 index 0000000..adb1d90 --- /dev/null +++ b/dist/ba_root/.bsac2 @@ -0,0 +1 @@ +FxVcDFEHVRIDVkxNEWsAWFYIAB9eXwRbAAUCUF1MB1kOUk9cAFhVHVdRBVxRChUHUFFcSxZbQ0FCTRJHFkxaBwoLBmdKV18RBQUYXl8NQUQDa2QBEwJfUUhaFXEnDwJTAFsdDFJPRxVaW11UFhc1GFZEBhBcCRwbWVgCWF1ZR0MCUwEaElEDQEczDwQYX1FFRRE3Eg1DVUEIXhUSTUhQDEBoQVUYVApeTBUWWFZJT0EDB1JoWlAbCA8fQ0MwRgdNVRsXAU0RXktVMVQCVFQPRxZeFlkYGE0RSFpQGwEUPEVfBVsKXENqWVAVVhoXBU0aE0FZH1EQfUYMBQwNVksUCxlGOTNWUwBdE1dEZnITW0gaTlsNVxEPAj0ETBgYU01XVB8JGAE/SEQEXlcLXg9eWE0PCzoDFhgYURZRGBRXGlJwGEEADQBBWVVFBxZEVhVjE1McG01EARMAUFBEURddDkhWEkcxWVUXBkdbAxQUUAEMDwlBVQ5XCE1DGxdKHB8aTlUMSAJEXwhHMVlVFwZHWwMUFFIDCRYNXlcNQUQDS0QBExFGSk5cAEsGXmsSVRZIFllSSUNDTURSCgUVCUQSWUlEWlhYX1ACR11fR09TEUJWDRZYVklPQQYJUkpXUhYBFB8ZSgxXRANLRFAdQ15RXlc3WQ9eaxJVFkgWWVNJQ15RRVI0BQofFQoYT0obXVBeUjNWWUliAFQQfkwHQAcPDlRUU1MGFBRcCxcFPlJRB2QHVUMbF0pDUltFdRZZEUkWK10MSBQkAggEQBoMBU5GFh5eUwYcD1pfV14fB19ZSmsTTRBeUQcWWBkEU09HAFBQd0YDFgJCcF8XEhJRVRlgXhdWSw8OWRRBXUoPVwcDVwsCFwBQTFNDEUoEGVleGhBcCAIJHR1DVUpEUQ9cN19BJ0MDX1A3CgYKVkxFE1hVU1wbEhNAD1pVF0RSDl1LA1INWQRyWw5dDkgWWVdVUR8aRkMLBwNCXlMMXBUXVlVMVj5eXVVdAldBFwxWBE4PVQALKQRSX0NUMgsPAkNDTWATV1FLQkQPVxhqWwUaWR4IShYDTlwvBgQGRl1mXgsKEh8ZYBFdRn9fVllTAF9UDWcJTRdCTRIWWB8ET0EEAlt5QVAQAEg+Ql4CQAlMXl0NfABATEhGQwJXARoHVwphUQIEEARjV19fFhdIPEVfQ30ISlxYWFYJRxh7XQJMDF9BRA5THRhBAgkIYmpwQwsBCAhiYi8QXBsUQn50M2V9f0lOWRJfBwUJRlZ3LCcgHBEUFEENEwMeZVENWQ9XV2lCWA9HSwNVAlAKSE4DWQdDQEFZUk0RWVJCTBcSDUVENVMKCxIDHB9RHxpMUBIWEFlZFEA0TFhSQV9RHQEPHUAUFAVUVU1RDlhCWE5FBEFLA0QASwBMVEQOUx8EU09HEVxPU0MwBQgHXl4EYglQXk1eHxVBV11cGAlBFw1KFhJCQwYRNwBdU19fBTQJBVlEEBwSS19JRUhTEQIcBE0aE0JPA0YwTFoICgsGY1dfXxYXSBhFXxNaHwoSAx8ETRFZTlwtXQJKTQNkDURaFxBLNVtdFnYQAQcYF2cCXgobCg0dHUNSW0V1FlkRSRYpUgQNbQwWRSZcGGJZBwpEVgMcQVMFUXxcTFYUVmhCXQ9MEANqE1oDX1sWDQFBZFFMUBAARFYGBU8QFktZWkgfCFBXQ0dPXg9MXzlFA1lVEUFfVQMIGhMSFg8PUh4AWgdLUVpZVBNAFkdREkwGX2cLRkAXBVZTVU0RWVVZLgEHC0JVM10PV0RKA34PQFRMQQZQFw1sFFULQ10NBEU3WltCXhAdRFYCHEFTBVFxTkxDBR1tT1ETGDFYVgdGDVhaB0MzCFBMWUMbRlxVGxICUQ54R1hfVU9xV0JZQX8MSEtGQApIFCcaCwBeUUJUQF5TQBVADEUDS2JYQ1oIXV99WwhWF14WEkYNXVwaUwRDCQkaEwcFFRhSQkEIAFhcSkgdQ1JbRXgEWQRYXTZbC0NAEE01E1ZbX0ILCwhMdV8OUA9XVxsXA1EfGkxXCXkUTEoCGjFZVRoKC0YTeVpYFAFEVg4cQVQUUFVXSWUTSn1VQBNZIlpZFFA2RFcIBhESEQICAVJIRBxFWQBXSF5RVEhCT11RQ14AZwVEXw5AQBcGVlNJQ1JbQlgUAQgJREMuUx4bCggDA1QfGl1GCFsGA1EFWwxeGgUPBAZsS1lEFgw5B1hCBlNEAwQJHR1DUltFdRZZEUkWKlURWRQwFwQPVxh7UBEQAx4VCloeRFhUSh8fDFJAYXcoVhdIShBVDg8OUlFVTRFbWV8WDQgZUmMXUxRNc1ZeRUMJCh0YQ0gRRFsDGgtOWw0QSwdfWVFuDAESBFJCD1MIXUMbFwVRAxQPRBNRAEgWElsXX1oCDgAPR2dTXxYWH04NBU8QFktZWkgfAltZX1UCTAZfS0hDC1dVEQc6DEEaDAdSVEpOVlMLcxFYQl0DYRNcGGJaElQCWF8OQEJ7XQAXChNKGgwETkYHD19xFFMUXR5qRVATWlZKFAhLQ25ZFF0MShZZUlBNEVlVWS4BBwtCVTNdD1dESgNiFVJBRFpGGCJBURBRQBcAU09HFVpbXVQWF1QtWl8WXBIbCgwdAU0RWU5cIE8CX1xIZBBCFCUMChVRWVpdQjIPD0NfEUtEAwYVD0ETWltIGghbDENLSFIOTFM8CgsFWlkUC1ZUVkAVUQBaKlxRXlhUMVxRQ0ASFjNfV0Z2DVVREUFfUwMUFEEQDQUJGVkAXQhKHl9BUAZsUUNQDlYGXlEHFlgZBFNPRwBQUHdGAxYCQmNYBhIxWFxVDwtXHxpMVwl0BkxfE1EyQl0NFxZPZ11XXEI0Cg1OVREQXAsAFQ9QAlt5WlUTXE1hWRVAQn5AAg0BQXRXUhNYVVJAFUARWwVcHlBOXg9AFktdE10BTFQKFlgfBFNTSUNSW159BwUBGVJgDFsITUMXal4VE0xFUUF1DFtdFRZYHgRPQR0MUksUCwQFCh9SHEFCFFBTXANYAlxWXhoHVAJKZxNfQBcAU1NJQ0NJf18WRlxdBwBTAkobUVpFfQRSX1hRMVcKQ0wVGjJfW0MlCg5HWlddDkQwBVREDEAfGwoIHR1DQ0pEVwQWCk5XCEdMS1gCBDoEVEFGRUBeUlwHHEFTAkoCF0BQGX97Dw5QCE0dFERVAUV4BgICFFZoWVgMEBVCeFYFEj9WRRlqXkFnUEhaQwJWARoWRgtOUU0KBg5dSxhVEAUBA1kSWQNUCQAVD1ACW3laVRNcLlhUEhZYGRhBAgYJck9XQwZKNh5SUwpBD1ZeGW9eDFFRQ1NDAlUBGhZGC05RTQANAEFZVUUHFhVCTV8GEFwKAAkBExFBUU5RT1EAQlYVGgxEWgkCOhJHWUQTWFxWXBsSBlwHW1xcbEERelZbXRVdEA8CEkYXSBhBAgYJck9XQwZKNANYWwpXRn9fVllTAF9UDWcJTRdCTRIWWBgYQQIGCXJPV0MGSjYeWBAlXQlNUlhBXUFgUFhADk0XDwJQGEBdRgoAAE9aW1lfEUoLGURYEV0JVBIDHARRHxpMVwl0BkxfE1EyQl0NFxZPYVdZWgsBRiNZQw9TE15YTQ1nCFBMQkYYGlkYFEREEERXBk0MAlxWRR8ECAcLaEMCRwJQb1hfUANaWQ8OVQhTARoWRgtOUU0ADQBBWVVFBxYVQl1RAFkLVkJeTF9DCQwdBE0aE19RBVFMRFcMDRZPW1laE1hXU1wbEgJWFQseVERfLXAaFwRPCE8PWQVcI1pVEQdLMkZIU0NCNBMCVFhBCFMVElhOWSBEWV9QT34PTE8KURFeFDUKBhVcSk8TWFBKTlZTC3MRWEJdA2UJVhhqRgRZFw1vB1gODw5aT0cRQVFVVEwNBQNZQ01BDUxcVQ8LUAMIHRhDSBFEWwMaD0xEEE0JAFhdaVcQDQEFUxJZBFMJHBtMUgl/XUxTFF0zQlEIQBEDZhYNBBNcTVhVQikHH0NVERBcCAAVD0UWQUxFR0MCGA9IFFtAFxYXCgYKVkxFBUBIRBxFXzxBB1VVGxcTFVpbRlEVS1cPRUoWEl9dAAZLBlJVU0JMAQcfQ1URbQNeV2ZFRA9HGhcMUQhPD1kFXC5IVQQWADFcUVhFEUogHlJVQ34JWFRcXxNbAggBFgBcEANVB0wubmcAAgkEEQIGH1NIRBxFWQBXSFBTVkNCT0VRRl0PXzxFXQpZB1kWWVJQUQMUFEEQDQUJGVkAXQhKHl9BUAZsTV4WWwxTHRREWQNVeAoNDiBQW1lEDBAVTg0FTxAHWlh4WlATVxZ/QQ9ZEUJNCFBCalsHQV9YHxpGQwsHA0JQXAxQB1VvSV9eB1pUSBZbCVMdFERVDkFbFCIGAlxNWEUuDQgHXl4EAEQDREtYVE0RSF9dAl1NQFkWR0xOVREXCg5dZ1BeEBA5AUUSWQpWCRwbXUMOYFlBUSVNEUxMD1sMYF0NFhEEQBoMAU5GEgVUWwZGFQ1xVEJED0caFwZUCFMBGgdXCmxDAhEBT3dNV11CMw8JW1QKXAEbCggdHUNBS0lGQwJSHRZWGEBdWxQGFzNSVl1YDAM2A15eF0FITUJWXVkYBxoXAFEUQUxbDnUVTEYHTTYUQ11EES8BAQ0XYBZcBVESAxsdQ0NKRFcEFgpOVwhHTEtYAgQ6AF9fU0MLBURWAwBTHkRYU1FhVABUTUhkDlENWUtIeANeQEMwEQBdXBZ8AxcSCUUSWQBWFRJNRFIKVkxeByBVDFhWEhZYHAZTU0lDQ0pfUgdKDw9YXhAcAFVRXnJTE1JCRFhDAlcdCEoWA05cLwYEBkZdZl4LChIfGWARXUZrRVdMQw5GVkkUN1EAWVcUTUAXBVZPRwBQUHdGAxYCQnheEF4HTFdRWRE1QVlEWghWBA1uD1cWQkYaQV9VHxpXUgolEQ1FVE10FFxVGWFeAFddXxZbCVMBGgdQEQNZAhspIhECAh9SSEQNVFgiRQdLVBd4UwRBGGJaElQCWF8OQEJ7XQAXChNKGgwGTkYWHl5TBhwSVkVLQ1AMVlZZawRWF19BOQBAFwZWT0cRQVFVVEwNBQNZQ01UClhXZkBQDVJBXl0AGlkZCFYYQF1GCgAAT0dXQ0MMBQsJWUQ8VwhNQkByA0MJCRgYQ0gRRFsDGhZCQRENBAxWVkJuBwoSHk5vUBBcCwAVD0ETWltIGhVXFl9WB1kHQ0A8BgsVQUFpAUBeU0AVQBFbBVweTUJEE11ZQFEPTDxIVhJGG3IFQVlUUR8aV1URShUEWEIXewhNVUtbUA1+TUFAQwJTAwpTGEBdRgoAAE9QUFdDAwcSCUVDTUYHVlJYQlwAQFtCQEMCVx0IVhhAXUYKAABPUFBXQwMHEglFQ01BB1dEWA8LUAUIHRhDShNdGlwFTB8YQQIGCXJPV0MGSjMOUkJDdAlWRFtMXQ0Ta0VBFVcWWRpcDE4PRBEKBgQdUVVeDBdIAVhfDRBcDAAJARMRQVdvWw9NEHlRBV8HWUdBWVNRAxQUUg0KEgVZRQZBK0xcTQ8LUx8aTFcJeRRMSgIaMEJbCAoAQXxWRV0DEQEEQxA1WwVNX0tUE1sHFA9VBWoMWVkSUS9CUAZBX1EfGkRBAQNEVgQAUx5EWFRKHx8MWlZhdyhWF0hKEFUODw5SW1VNEUpGUkBeUlwHHEFTBVF8XExWFFZoQl0PTBADeglMB18WWVJVTRFIRFgBAUgPX1ERUwVNVUteHwJcT3JZExpZGg1WGEBdRgoAAE9aW1lfEUoAAFZXPFsUWF4bFwVRAxQPUQ9ZAUFdJVsMWV0NFgASEQJQUA4XA0AVQBFbBVweUE5eD0AWRVEAShcPAlQEUgEWAgANIERZRFVMJgkUUkJBCFIVEklfWAJWFk5cAEoCTkwDRhEDUhEMFhVKGgwAUlRWQBVRB0FUF11YVX0iYFtMWAQaWR0WXwFOD1UACykEUl9DVDILDwJDQ01mDlwQbkxdDRECHwFNGhFeVRUWWBgEU09HAF1ZWkgWDQUfdHJBCBJLRVwBEwBQUGFRAF8WSGgJXQxZR002BwRBGHBeDRAEDVtcQ2QPWkRWX0hDCQodGENIEURbAxoBRVURAgYVVkpFHwMDAwJDElkLVgkcG0xSCX9dTFMUXTNCUQhAEQNnFhMAExNoQ18BDERWBgBPEBZLWVpIHwhQV0NHT14PTF85UhBMWgAGR1sHCAYdQBQUBVRVTVEOWEJYTkUEQUsDVgRKDUxKAhZYFQRTT0cAUFB6VAMDEwlnXwpcEkoebE9UExN3Q0cNWRZKUBIUNERXFwwXGBECBAFORgcCU0JBCB0bU1tfE1tHSlhRTRoCW1VEDlIDAU9BDAxBXhQLUkpWXAIcQVEEXRIDWUMUVhQPVQ9UB0EaXAZSARYKDgNDCQkYAU5GBwAVChdAE1wcG0xfDVdLDw5UFEFOTBAWWFlGFgZJQ0dTFAsEBQofUk1PEBZLWVpIHwhQV0NHT14PTF85RApEWAoTFQhdXUUTWFBWXBsSE0APWlUXTlkAQVlOQARKEANID0wLSBZZUlFRAxQUQRANBQkZWQBdCEoeX0FQBmxIQlgAVgcPAlIEUgEWAgcmDl5IWlQWASUuFQoFUwpKVRUPUAJbdEhVBk0GfVcPWhZeGiwNFg1STVFZFkQxBU1REVZEAwEMARMRXE9IRjNZDUZRCFMyQl0NFxZPR0pZQQodVg4VClEeRFhTUWFUAFRNSGQOUQ1ZS0h4A15AQzARAF1cFmYLHgceUxJZBlYVEklfWAJWFkRXDlYQA14KVQVyVwINBAVSGgwFUlRKTkdCClEDF1NRTEMAUExIRhIWCENRAVwWclkRQV9QAwgGHUAFBQR7VQJVE1xgVkRfFUAWfkERXRENdQNTAw1kFg0GCRECBwRORhYeXlMGHAVRUUtMUhVWSl4aDF0PDwJTBFIBFgIADSBEWURVTC0ITHRfDUYUVlwbFwRNEUhfXQJdTUpZC1ERA0cNDBIDUlRabgQNAQRDbw5ARAMICR0dQ1JbRXgEWQRYXTZbC0NAEE0qD0BUV0QFDBJMcF8HEFwKABUPUAJbdEhVBk0GfVcPWhZeGjEMCgpaXRZ3DQsSDlZcDxIwUFNNQkMYEQIcBE0aE19RBVFMTlwCEQQCR11EQkwTDxZWQgcQXAgGCR0dQ10aFxYsVwdJXRRHTg1XCwYGChNXQ0VCBgcAW1kQRg9aURdDVBUTXkJGQVcTSFZLRw1YRgAGRQNcVVRCExEHCBdEDF0KShAfDUICQVFdQBIWP0MEBEZccVolDAkNXE8Wcw0JBD9GRQJWRlZeGWtQAlZaQlsKAkNLWQVRAEJbCE0GDl4XdF4PBjUdQlEHdQdUVWVDDQNBBnFaJ1cWQ1xGVUJPQQRDChMTUFdHB0QHTEZFBkESUF9XEhEkXllEWEFLFl1ICUYWbVIRDAAMX1FYVkwKAxgVHEFCFFBTXANYAlxWXhoHVAJKZxVdDEpVEwwXBBECAgFSSEQNVFgvVwdeRVx9XghdTF4aI1cMQBghWwdeFBcLAEF3QVhQDw0SCRUKUgdKG1FaRX0EUl9YUTFXCkNMFRo2Y2BDNwATQVdEE1hXVkAVQBFbBVweUE5eD0AWXkQIXAZfGlwDUh0YQRMXCFBdGFgBCwgfGVYPUwFmU0NIUglsSkhEFFoPRFtEDlYdBE9BFRNaW1MfCwcJAkQeBVcCVkJYDwtXAwgBFhFKCk5dSF0BQloQTQMNUl9pUgoNCA0VClcCVhUSSV9YAlYWRFcOVhADXgpVBXJTBhEIAF1BFAtWVFZAFUMCXgNKEgNWTE0RSF9dAl1NRFsJWhEDXAYPCARHGgwCUFRWQBVRAFoqXFFeWFQxXFFDQBIWMEVZFF0MShQKEEUiUkpfXwVGXF0CHEFRCVdEUENEBEB3S1ISXRcPAlYYQFldAAgAFUAJd1wNEQgYFQpWAkobQEtEUgQdUU5bD0tNS1QHUz1HVRMCC0MJDAYBTkYWHl5TBhwFUVFLTFIVVkpeGhJWAkZdFVwDSVsUQV9XAwgaEwMHDi1AURFWSGlCVg1jFF1ZX1sUVgcNbg9XFkJGGkFfVx8aV1IKKAMNUEUGYglQXk1eHzNcV0ZdBBglQlcSVgNBWEMwDRRHV0NFQF5XWRsSF1sFUlVNXgQgXldYWhUaWRsNVgROD1UACykEUl9DVDILDwJDQ010ClhHVUhCEhNuRFcVVxFUGlwFVwEWEBMKD0BXRGULBw0JQ0NBCFcMHBtdQwhQXQNdAlcNXhYDTQdPVQ8PR1sBDQYdQAUCHwUeDlsIdXNqTlANVhoXBE8MTw9ZBVwuSFUEFgAxXFFYRRFKKwVZVUN1B1RVSg8LUAMUD0QTUQBIFg9XDUNHTQUJAFRnQ1AHRlxYBwBPEBZLWVpIHwJbWV9VAkwGX0tIVxtPWxEER1sBDQYBTkYWHlhgDEUDS2JYQ1oIXV9vWw5LFw8CVwROD1UACykEUl9DVDILDwJDQ012E1hcGXpYBF9cRFoGGlkcCEoWEl9dAAZLAltZRFABEAMeRB4BXQhcQxsXBlEDFA9VBUtNQFEIeCFkWhcGFxdSVBQLUVJWQBVRAFonTlFLSR80UV1fFCdXDFlaB1gODWIKABEOQUEUC1pIRA1UWCJFB0tUF2peDVcYYF0PXREPAlEYQF1GCgAAT1pbWV8RSgUeWEcNEFwPBAkdHUNSW0V1FlkRSRYyUQNAFDMPBBhWShQLUFRKTlZTFxBcX1FVXlRNEUhfXQJdTURbCVoRA1IPAgI+WE1BUAsQRFYDAFMeRFhTUWxGAEFcA3sPSw9MTQFcFg15AhARBEEaDAVORgcPX3wGUwFMVWlCWA9HSwNhA10RDX4JWxZPVQ8PRTJbTUJeFxBEVgMATxAHWlh4WlATVxZ5ejUYN0hKFFsQDw5UT0cAUFB6VAMDEwlnXwpcEkoefkJdBRN1RFoESkEXC1YYQExXCy8AAFRNU2ENDQgYRB4sXBVVUUxKWRUTdUxHFV0RDwJXBE4PVQcQSwxaVnpyQF5WQgccQUIUUFNcA0QRVEpMUARLTV1KCRZYHARTU0lDUltefQcFARlSYAxbCE1DF2RfQXBXQ0ATVw8PAlMYQExXCy8AAFRNU2ENDQgYRB4vUxVNEGpZUA9XGGpbBRpZGAhKFhJfXQAGSwhQV1hCTAIKDVBvCkYHVUkbFwVRAxQPRBNRAEgWD1cNQ0dNBQkAVGdXRBEQFA1bWQIQXA0ACQETE0NIShZbCU0aFERVAUV1FAIXBR1oRF5CJgkUUkJBCFMVElhOWSBEWV9QT2oMQlMPUUJrWwwXBwBfVBZnCwcSA0VJQQhTFRJJX1gCVhZKVQxdEANMB0YFSEA8ExcAUExfUgdGXFsCAE8QB1pYeFpQE1cWYloSVAJYXw5AQnpdGQIXBRECAB1AAhQFUl4HZhRAZFBOWgRHSw8OVQhTARoHUBEDWQoNKSJgW1ddB0ZcXBkBTxAWS1laSB8GUlVIR09VBlldCUY9XlwMFAATEQIDAVJIRB9eVw17CG1ZWkZUFUAaF29QCE8cDUoFWgEGU09XUx8KBR1QUEpeAm1PEAdaWHhaUBNXFmJaElQCWF8OQEJqWwdBX1gfGldVEUoLDU98IHsITVVLW1ANEQIfAFEUQV1KD1cHA1cLAhcAUExTQxFKDR5YXggQXAsACQETEUFRTlFPUQBCVhUaG0RaGgILBhECBQFSSEQNVFgiRQdLVBdhUBJHGH5AAFYHDW8PTgNfUEFZVFAfGldSCigDDVBFBmIJUF5NXh80UV1fFDNNDUxKCUEMSRQ1CgYVXEpPE1hWVkAVQBFbBVweUE5eD0AWS1gAXzxMSgFRDFldDQJHWwcIBh1ABQUEdkcCQAIXYkxDUBNcTUNQQW8KV1kUUEAXAh5PRwxaS1VjBwUCOlZcEAA1TVFNSBNbARQPWQhLAH9dB1A0TFgQUUdbSBpaWAwPAwh2UwBdE1dESh8TW2hDD1pDAkF+XRRCB18DVVJUVgQMFB1AAERWFWwWV1YKAGpIQxdWShoCUAlUGgxEGEBEUEFZRxFRFXtdVi0zPnZ5NUo0c2h/d1UkYVx+bCRQJHxtAXk1QV81NiQwfWBBaDAiN11UYyFoLnhHeGkTHG4UD0AKCkEXY0RAUAB/D1sPNlkNXHcJHAI6X3o0YCAMYW0ccyURZQEWDVENRl0CdQFOWxYNERIRAm0TPhEDXAQAMFcUT1VLGgdQAg8aAENlTw9MDRZYD0AIAkgqXwBcZghRDCpQDV4QShtCXF5eDUVdSXUCWwxYVhJ9Jg8OQRMHTHp+Al43VlIDdkdeD0RETUQ= \ No newline at end of file diff --git a/dist/ba_root/.bsuuid b/dist/ba_root/.bsuuid new file mode 100644 index 0000000..359130f --- /dev/null +++ b/dist/ba_root/.bsuuid @@ -0,0 +1 @@ +70c2f909-1a38-4a8c-8f4b-4ccea3861bdf \ No newline at end of file diff --git a/dist/ba_root/mods/aarch64/BridgitMash.so b/dist/ba_root/mods/aarch64/BridgitMash.so index bca18b5..ea449de 100644 Binary files a/dist/ba_root/mods/aarch64/BridgitMash.so and b/dist/ba_root/mods/aarch64/BridgitMash.so differ diff --git a/dist/ba_root/mods/features/afk_check.py b/dist/ba_root/mods/features/afk_check.py index 5d35251..37fb316 100644 --- a/dist/ba_root/mods/features/afk_check.py +++ b/dist/ba_root/mods/features/afk_check.py @@ -9,15 +9,28 @@ settings = setting.get_settings_data() INGAME_TIME=settings["afk_remover"]["ingame_idle_time_in_secs"] LOBBY_KICK=settings['afk_remover']["kick_idle_from_lobby"] INLOBBY_TIME=settings['afk_remover']["lobby_idle_time_in_secs"] +cIdle = 0 # players/player found idle within very short time +cLastIdle = 0 class checkIdle(object): def start(self): self.t1=ba.timer(2, ba.Call(self.check),repeat=True) self.lobbies={} def check(self): + global cLastIdle + global cIdle current=ba.time(ba.TimeType.REAL,timeformat=ba.TimeFormat.MILLISECONDS) for player in ba.internal.get_foreground_host_session().sessionplayers: last_input=int(player.inputdevice.get_last_input_time()) afk_time=int((current-last_input)/1000) + if afk_time in range(INGAME_TIME,INGAME_TIME+20) or afk_time > INGAME_TIME+20: + if (current - cLastIdle)/1000 < 3: + cIdle = cIdle+1 + if cIdle >= 3: + return + else: + cIdle = 0 + cLastIdle = current + if afk_time in range(INGAME_TIME,INGAME_TIME+20): self.warn_player(player.get_v1_account_id(),"Press any button within "+str(INGAME_TIME+20-afk_time)+" secs") if afk_time > INGAME_TIME+20: diff --git a/dist/bombsquad_headless b/dist/bombsquad_headless index 4144e3a..c478646 100644 Binary files a/dist/bombsquad_headless and b/dist/bombsquad_headless differ diff --git a/dist/bombsquad_headless_aarch64 b/dist/bombsquad_headless_aarch64 index 0446499..b6f4df0 100644 Binary files a/dist/bombsquad_headless_aarch64 and b/dist/bombsquad_headless_aarch64 differ