diff --git a/bombsquad_server b/bombsquad_server index a28b9de..5049230 100644 --- a/bombsquad_server +++ b/bombsquad_server @@ -1,7 +1,7 @@ -#!/usr/bin/env -S python3.8 -O +#!/usr/bin/env python3.9 # Released under the MIT License. See LICENSE for details. # -"""BombSquad server manager.""" +"""BallisticaCore server manager.""" from __future__ import annotations import json @@ -28,7 +28,7 @@ from efro.error import CleanError from efro.terminal import Clr if TYPE_CHECKING: - from typing import Optional, List, Dict, Union, Tuple + from typing import Optional, Union from types import FrameType from bacommon.servermanager import ServerCommand @@ -36,7 +36,7 @@ VERSION_STR = '1.3' # Version history: # 1.3.1 -# Windows binary is now named BombSquadHeadless.exe +# Windows binary is now named BallisticaCoreHeadless.exe # 1.3: # Added show_tutorial config option # Added team_names config option @@ -63,10 +63,10 @@ VERSION_STR = '1.3' class ServerManagerApp: - """An app which manages BombSquad server execution. + """An app which manages BallisticaCore server execution. Handles configuring, launching, re-launching, and otherwise - managing BombSquad operating in server mode. + managing BallisticaCore operating in server mode. """ # How many seconds we wait after asking our subprocess to do an immediate @@ -81,7 +81,7 @@ class ServerManagerApp: self._interactive = sys.stdin.isatty() self._wrapper_shutdown_desired = False self._done = False - self._subprocess_commands: List[Union[str, ServerCommand]] = [] + self._subprocess_commands: list[Union[str, ServerCommand]] = [] self._subprocess_commands_lock = Lock() self._subprocess_force_kill_time: Optional[float] = None self._auto_restart = True @@ -126,7 +126,7 @@ class ServerManagerApp: dbgstr = 'debug' if __debug__ else 'opt' print( - f'{Clr.CYN}{Clr.BLD}BombSquad server manager {VERSION_STR}' + f'{Clr.CYN}{Clr.BLD}BallisticaCore server manager {VERSION_STR}' f' starting up ({dbgstr} mode)...{Clr.RST}', flush=True) @@ -251,8 +251,8 @@ class ServerManagerApp: def screenmessage(self, message: str, - color: Optional[Tuple[float, float, float]] = None, - clients: Optional[List[int]] = None) -> None: + color: Optional[tuple[float, float, float]] = None, + clients: Optional[list[int]] = None) -> None: """Display a screen-message. This will have no name attached and not show up in chat history. @@ -265,7 +265,7 @@ class ServerManagerApp: def chatmessage(self, message: str, - clients: Optional[List[int]] = None) -> None: + clients: Optional[list[int]] = None) -> None: """Send a chat message from the server. This will have the server's name attached and will be logged @@ -403,7 +403,7 @@ class ServerManagerApp: out = ( f'{Clr.BLD}{filename} usage:{Clr.RST}\n' + cls._par( 'This script handles configuring, launching, re-launching,' - ' and otherwise managing BombSquad operating' + ' 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' @@ -513,7 +513,7 @@ class ServerManagerApp: f"Config file not found: '{self._config_path}'.") import yaml - with open(self._config_path) as infile: + 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. @@ -533,7 +533,7 @@ class ServerManagerApp: flush=True) return out - def _enable_tab_completion(self, locs: Dict) -> None: + def _enable_tab_completion(self, locs: dict) -> None: """Enable tab-completion on platforms where available (linux/mac).""" try: import readline @@ -575,8 +575,8 @@ class ServerManagerApp: os.environ['BA_SERVER_WRAPPER_MANAGED'] = '1' print(f'{Clr.CYN}Launching server subprocess...{Clr.RST}', flush=True) - binary_name = ('BombSquadHeadless.exe' - if os.name == 'nt' else './bombsquad_headless') + binary_name = ('BallisticaCoreHeadless.exe' + if os.name == 'nt' else './ballisticacore_headless') assert self._ba_root_path is not None self._subprocess = None @@ -646,13 +646,13 @@ class ServerManagerApp: 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) as infile: + with open(cfgpath, encoding='utf-8') as infile: bincfg = json.loads(infile.read()) else: bincfg = {} # Some of our config values translate directly into the - # bombsquad config file; the rest we pass at runtime. + # 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 @@ -668,7 +668,7 @@ class ServerManagerApp: del bincfg['Custom Team Colors'] bincfg['Idle Exit Minutes'] = self._config.idle_exit_minutes - with open(cfgpath, 'w') as outfile: + with open(cfgpath, 'w', encoding='utf-8') as outfile: outfile.write(json.dumps(bincfg)) def _enqueue_server_command(self, command: ServerCommand) -> None: @@ -855,7 +855,7 @@ class ServerManagerApp: def main() -> None: - """Run the BombSquad server manager.""" + """Run the BallisticaCore server manager.""" try: ServerManagerApp().run() except CleanError as exc: diff --git a/bombsquad_server.py b/bombsquad_server.py deleted file mode 100644 index a28b9de..0000000 --- a/bombsquad_server.py +++ /dev/null @@ -1,869 +0,0 @@ -#!/usr/bin/env -S python3.8 -O -# Released under the MIT License. See LICENSE for details. -# -"""BombSquad 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 - -# 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 typing import Optional, List, Dict, Union, Tuple - from types import FrameType - from bacommon.servermanager import ServerCommand - -VERSION_STR = '1.3' - -# Version history: -# 1.3.1 -# Windows binary is now named BombSquadHeadless.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 BombSquad server execution. - - Handles configuring, launching, re-launching, and otherwise - managing BombSquad 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[Union[str, ServerCommand]] = [] - self._subprocess_commands_lock = Lock() - self._subprocess_force_kill_time: Optional[float] = None - self._auto_restart = True - self._config_auto_restart = True - self._config_mtime: Optional[float] = None - self._last_config_mtime_check_time: Optional[float] = None - self._should_report_subprocess_error = False - self._running = False - self._interpreter_start_time: Optional[float] = None - self._subprocess: Optional[subprocess.Popen[bytes]] = None - self._subprocess_launch_time: Optional[float] = None - self._subprocess_sent_config_auto_restart = False - self._subprocess_sent_clean_exit = False - self._subprocess_sent_unclean_exit = False - self._subprocess_thread: Optional[Thread] = None - self._subprocess_exited_cleanly: Optional[bool] = 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}BombSquad 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: Optional[Tuple[float, float, float]] = None, - clients: Optional[List[int]] = 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: Optional[List[int]] = 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: Optional[int] = 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 BombSquad 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: Optional[ServerConfig] = 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) 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: - """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 = ('BombSquadHeadless.exe' - if os.name == 'nt' else './bombsquad_headless') - assert self._ba_root_path is not None - self._subprocess = None - - # Launch! - try: - 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) as infile: - bincfg = json.loads(infile.read()) - else: - bincfg = {} - - # Some of our config values translate directly into the - # bombsquad 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') 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 - - # 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: Optional[int] = 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: Optional[float] - 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 BombSquad 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) - - -if __name__ == '__main__': - main() diff --git a/config.yaml b/config.yaml index 92cacf9..eba53e3 100644 --- a/config.yaml +++ b/config.yaml @@ -1,30 +1,30 @@ # To configure your server, create a config.yaml file in the same directory -# as the bombsquad_server script. The config_template.yaml file can be +# as the ballisticacore_server script. The config_template.yaml file can be # copied or renamed as a convenient starting point. # Uncomment any of these values to override defaults. # Name of our server in the public parties list. -party_name: SUDO | Teams +party_name: smoothy # If true, your party will show up in the global public party list # Otherwise it will still be joinable via LAN or connecting by IP address. -party_is_public: true +#party_is_public: true # If true, all connecting clients will be authenticated through the master # server to screen for fake account info. Generally this should always # be enabled unless you are hosting on a LAN with no internet connection. -#authenticate_clients: true +authenticate_clients: true # IDs of server admins. Server admins are not kickable through the default # kick vote system and they are able to kick players without a vote. To get # your account id, enter 'getaccountid' in settings->advanced->enter-code. -#admins: -#- pb-yOuRAccOuNtIdHErE -#- pb-aNdMayBeAnotherHeRE +admins: +- pb-yOuRAccOuNtIdHErE +- pb-aNdMayBeAnotherHeRE # Whether the default kick-voting system is enabled. -#enable_default_kick_voting: true +enable_default_kick_voting: true # UDP port to host on. Change this to work around firewalls or run multiple # servers on one machine. @@ -37,17 +37,18 @@ port: 43210 # multiple controllers. Also, this number currently includes the server so # generally make it 1 bigger than you need. Max-players is not currently # exposed but I'll try to add that soon. -max_party_size: 8 +max_party_size: 6 -# Options here are 'ffa' (free-for-all) and 'teams' +# Options here are 'ffa' (free-for-all), 'teams' and 'coop' (cooperative) # This value is ignored if you supply a playlist_code (see below). #session_type: ffa +# Playlist-code for teams or free-for-all mode sessions. # To host your own custom playlists, use the 'share' functionality in the # playlist editor in the regular version of the game. # This will give you a numeric code you can enter here to host that # playlist. -#playlist_code: 12345 +playlist_code: 12345 # Alternately, you can embed playlist data here instead of using codes. # Make sure to set session_type to the correct type for the data here. @@ -60,6 +61,15 @@ max_party_size: 8 # (teams mode only). #auto_balance_teams: true +# The campaign used when in co-op session mode. +# Do print(ba.app.campaigns) to see available campaign names. +#coop_campaign: Easy + +# The level name within the campaign used in co-op session mode. +# For campaign name FOO, do print(ba.app.campaigns['FOO'].levels) to see +# available level names. +#coop_level: Onslaught Training + # Whether to enable telnet access. # IMPORTANT: This option is no longer available, as it was being used # for exploits. Live access to the running server is still possible through @@ -70,11 +80,11 @@ max_party_size: 8 # Series length in teams mode (7 == 'best-of-7' series; a team must # get 4 wins) -#teams_series_length: 7 +teams_series_length: 7 # Points to win in free-for-all mode (Points are awarded per game based on # performance) -#ffa_series_length: 24 +ffa_series_length: 24 # If you have a custom stats webpage for your server, you can use this # to provide a convenient in-game link to it in the server-browser @@ -109,12 +119,12 @@ max_party_size: 8 #show_tutorial: false # Team names (teams mode only). -#team_names: -#- Blue -#- Red +team_names: +- ladoo +- barfi # Team colors (teams mode only). -#team_colors: -#- [0.1, 0.25, 1.0] -#- [1.0, 0.25, 0.2] +team_colors: +- [2, 0.25, 1.0] +- [1.0, 0.25, 0.2] diff --git a/dist/BombSquadHeadless.exe b/dist/BombSquadHeadless.exe deleted file mode 100644 index 0423347..0000000 Binary files a/dist/BombSquadHeadless.exe and /dev/null differ diff --git a/dist/DLLs/_asyncio.pyd b/dist/DLLs/_asyncio.pyd deleted file mode 100644 index a622ebf..0000000 Binary files a/dist/DLLs/_asyncio.pyd and /dev/null differ diff --git a/dist/DLLs/_bz2.pyd b/dist/DLLs/_bz2.pyd deleted file mode 100644 index 81197c5..0000000 Binary files a/dist/DLLs/_bz2.pyd and /dev/null differ diff --git a/dist/DLLs/_ctypes.pyd b/dist/DLLs/_ctypes.pyd deleted file mode 100644 index 5d56a63..0000000 Binary files a/dist/DLLs/_ctypes.pyd and /dev/null differ diff --git a/dist/DLLs/_ctypes_test.pyd b/dist/DLLs/_ctypes_test.pyd deleted file mode 100644 index 3df5a1f..0000000 Binary files a/dist/DLLs/_ctypes_test.pyd and /dev/null differ diff --git a/dist/DLLs/_decimal.pyd b/dist/DLLs/_decimal.pyd deleted file mode 100644 index 37980ba..0000000 Binary files a/dist/DLLs/_decimal.pyd and /dev/null differ diff --git a/dist/DLLs/_elementtree.pyd b/dist/DLLs/_elementtree.pyd deleted file mode 100644 index 6e6351b..0000000 Binary files a/dist/DLLs/_elementtree.pyd and /dev/null differ diff --git a/dist/DLLs/_hashlib.pyd b/dist/DLLs/_hashlib.pyd deleted file mode 100644 index 4eee523..0000000 Binary files a/dist/DLLs/_hashlib.pyd and /dev/null differ diff --git a/dist/DLLs/_lzma.pyd b/dist/DLLs/_lzma.pyd deleted file mode 100644 index 692e2fd..0000000 Binary files a/dist/DLLs/_lzma.pyd and /dev/null differ diff --git a/dist/DLLs/_msi.pyd b/dist/DLLs/_msi.pyd deleted file mode 100644 index b92d08f..0000000 Binary files a/dist/DLLs/_msi.pyd and /dev/null differ diff --git a/dist/DLLs/_multiprocessing.pyd b/dist/DLLs/_multiprocessing.pyd deleted file mode 100644 index b8d37f7..0000000 Binary files a/dist/DLLs/_multiprocessing.pyd and /dev/null differ diff --git a/dist/DLLs/_overlapped.pyd b/dist/DLLs/_overlapped.pyd deleted file mode 100644 index f57b89d..0000000 Binary files a/dist/DLLs/_overlapped.pyd and /dev/null differ diff --git a/dist/DLLs/_queue.pyd b/dist/DLLs/_queue.pyd deleted file mode 100644 index 1e03d25..0000000 Binary files a/dist/DLLs/_queue.pyd and /dev/null differ diff --git a/dist/DLLs/_socket.pyd b/dist/DLLs/_socket.pyd deleted file mode 100644 index 0bfe2be..0000000 Binary files a/dist/DLLs/_socket.pyd and /dev/null differ diff --git a/dist/DLLs/_sqlite3.pyd b/dist/DLLs/_sqlite3.pyd deleted file mode 100644 index 9aa65e4..0000000 Binary files a/dist/DLLs/_sqlite3.pyd and /dev/null differ diff --git a/dist/DLLs/_ssl.pyd b/dist/DLLs/_ssl.pyd deleted file mode 100644 index 2f1373b..0000000 Binary files a/dist/DLLs/_ssl.pyd and /dev/null differ diff --git a/dist/DLLs/_testbuffer.pyd b/dist/DLLs/_testbuffer.pyd deleted file mode 100644 index 547449b..0000000 Binary files a/dist/DLLs/_testbuffer.pyd and /dev/null differ diff --git a/dist/DLLs/_testcapi.pyd b/dist/DLLs/_testcapi.pyd deleted file mode 100644 index df7abae..0000000 Binary files a/dist/DLLs/_testcapi.pyd and /dev/null differ diff --git a/dist/DLLs/_testconsole.pyd b/dist/DLLs/_testconsole.pyd deleted file mode 100644 index 2036004..0000000 Binary files a/dist/DLLs/_testconsole.pyd and /dev/null differ diff --git a/dist/DLLs/_testimportmultiple.pyd b/dist/DLLs/_testimportmultiple.pyd deleted file mode 100644 index 22bafd1..0000000 Binary files a/dist/DLLs/_testimportmultiple.pyd and /dev/null differ diff --git a/dist/DLLs/_testmultiphase.pyd b/dist/DLLs/_testmultiphase.pyd deleted file mode 100644 index 8a0ec5f..0000000 Binary files a/dist/DLLs/_testmultiphase.pyd and /dev/null differ diff --git a/dist/DLLs/_tkinter.pyd b/dist/DLLs/_tkinter.pyd deleted file mode 100644 index 22f8131..0000000 Binary files a/dist/DLLs/_tkinter.pyd and /dev/null differ diff --git a/dist/DLLs/libcrypto-1_1.dll b/dist/DLLs/libcrypto-1_1.dll deleted file mode 100644 index 103d153..0000000 Binary files a/dist/DLLs/libcrypto-1_1.dll and /dev/null differ diff --git a/dist/DLLs/libffi-7.dll b/dist/DLLs/libffi-7.dll deleted file mode 100644 index ab77c4d..0000000 Binary files a/dist/DLLs/libffi-7.dll and /dev/null differ diff --git a/dist/DLLs/libssl-1_1.dll b/dist/DLLs/libssl-1_1.dll deleted file mode 100644 index 110ac97..0000000 Binary files a/dist/DLLs/libssl-1_1.dll and /dev/null differ diff --git a/dist/DLLs/pyexpat.pyd b/dist/DLLs/pyexpat.pyd deleted file mode 100644 index ed3d3ea..0000000 Binary files a/dist/DLLs/pyexpat.pyd and /dev/null differ diff --git a/dist/DLLs/python_lib.cat b/dist/DLLs/python_lib.cat deleted file mode 100644 index 11e40dd..0000000 Binary files a/dist/DLLs/python_lib.cat and /dev/null differ diff --git a/dist/DLLs/python_tools.cat b/dist/DLLs/python_tools.cat deleted file mode 100644 index d242cdd..0000000 Binary files a/dist/DLLs/python_tools.cat and /dev/null differ diff --git a/dist/DLLs/select.pyd b/dist/DLLs/select.pyd deleted file mode 100644 index 95042bb..0000000 Binary files a/dist/DLLs/select.pyd and /dev/null differ diff --git a/dist/DLLs/sqlite3.dll b/dist/DLLs/sqlite3.dll deleted file mode 100644 index 72565ae..0000000 Binary files a/dist/DLLs/sqlite3.dll and /dev/null differ diff --git a/dist/DLLs/sqlite3_d.dll b/dist/DLLs/sqlite3_d.dll deleted file mode 100644 index 91032a3..0000000 Binary files a/dist/DLLs/sqlite3_d.dll and /dev/null differ diff --git a/dist/DLLs/tcl86t.dll b/dist/DLLs/tcl86t.dll deleted file mode 100644 index 3c78e2d..0000000 Binary files a/dist/DLLs/tcl86t.dll and /dev/null differ diff --git a/dist/DLLs/tk86t.dll b/dist/DLLs/tk86t.dll deleted file mode 100644 index d401cf9..0000000 Binary files a/dist/DLLs/tk86t.dll and /dev/null differ diff --git a/dist/DLLs/unicodedata.pyd b/dist/DLLs/unicodedata.pyd deleted file mode 100644 index 2b975e4..0000000 Binary files a/dist/DLLs/unicodedata.pyd and /dev/null differ diff --git a/dist/DLLs/winsound.pyd b/dist/DLLs/winsound.pyd deleted file mode 100644 index a98b1ff..0000000 Binary files a/dist/DLLs/winsound.pyd and /dev/null differ diff --git a/dist/ba_data/data/langdata.json b/dist/ba_data/data/langdata.json index 04e13eb..119e80a 100644 --- a/dist/ba_data/data/langdata.json +++ b/dist/ba_data/data/langdata.json @@ -1,6 +1,6 @@ { "lang_names_translated": { - "Arabic": "عربى", + "Arabic": "العربية", "Belarussian": "Беларуская", "Chinese": "简体中文", "ChineseTraditional": "繁體中文", @@ -28,6 +28,7 @@ "Slovak": "Slovenčina ", "Spanish": "Español", "Swedish": "Svenska", + "Thai": "ภาษาไทย", "Turkish": "Türkçe", "Ukrainian": "Українська", "Venetian": "Veneto", @@ -35,10 +36,12 @@ }, "translation_contributors": [ "!ParkuristTurist!", + "\"9۝ÅℳЇℜρℜѺ۝ƬǀGΞЯ", "/in/dev/", "1.4.139", "123", "123123123", + "228варенье", "233", "26885", "43210", @@ -60,14 +63,16 @@ "abhi", "AbhinaY", "Gifasa abidjahsi", + "Abinav", "Abir", + "Abolfadl", "Abraham", "Roman Abramov", "AC", "adan", "Adeel (AdeZ {@adez_})", "Adel", - "Rio adi", + "Rio Adi", "Rayhan Adiansyah", "Yonas Adiel", "admin", @@ -90,11 +95,12 @@ "Collin Ainge", "Akbar", "Bekir Akdemir", + "Akhanyile", "Aki", "Abdullah Akkan", "Berk Akkaya", "AKYG", - "mohammed al-abri", + "Mohammed al-abri", "Ali Al-Gattan", "alaa", "Anna Alanis", @@ -128,13 +134,16 @@ "altidor", "Oguz Altindal", "aly", + "Shahin Amani", "Amar", "alfredo jasper a ambel", "Amedeo", + "Kidane Amen-Allah", "amin.ir", "Amir", "amir22games", "amir234", + "AmirMahdi.D :P", "Amirul", "Ange Kevin Amlaman", "amr", @@ -159,6 +168,7 @@ "André", "Andy", "krish angad", + "Krishna D Angad", "vân anh", "Aniol", "Anmol", @@ -170,6 +180,8 @@ "apis", "Sagar April", "Fernando Araise", + "Arda (Frosty)", + "Hellmann Arias", "Muhammad Arief", "Arin", "ARSHAD", @@ -179,6 +191,7 @@ "Ashish", "Asraf", "Asshold", + "Eliane Santos de assis", "Atalanta", "Atilla", "Atom", @@ -197,6 +210,7 @@ "Azoz", "Burak Karadeniz (Myth B)", "Myth B.", + "B4likeBefore", "Balage8", "BalaguerM", "Peter Balind", @@ -231,7 +245,9 @@ "Bendy", "Sérgio Benevides", "Simon Bengtsson", + "Alfano Beniamino", "Benjamin", + "Benjamín", "benjapol", "Ori bennov", "benybrot96", @@ -269,6 +285,7 @@ "Anderson Brito", "Broi", "Brojas", + "Brojasko", "BrotheRuzz11", "bsam", "Bsamhero", @@ -302,8 +319,12 @@ "chang", "Charlie", "kalpesh chauhan", + "chausony", "CheesySquad", + "ChocolateComrade", "choi", + "Vadim Choi", + "Chris71/Chris71x", "Hans Christensen", "Attilio Cianci", "Kajus Cibulskis", @@ -312,6 +333,8 @@ "Nick Clime", "Jerome Collet", "probably my. com", + "Stefano Corona", + "Corrolot", "Francisco Law Cortez", "David Cot", "Nayib Méndez Coto", @@ -321,6 +344,7 @@ "CrazyBear", "Frederick Cretton", "crisroco10", + "Cristhian", "Cristian", "Cristóbal", "Criz", @@ -328,18 +352,21 @@ "Prashanth CrossFire", "Cryfter", "cukomus", + "CYCL0YT", "D", "Dada", "Daivaras", "Dakkat", "Mikkel Damgaard", "Danco", + "Dani", "Daniel", "Daniel3505", "Dančo", "Iman Darius", "DarkAnarcy", "DarkEnergon8", + "DarshaN", "Shibin das", "Dasto", "Davide", @@ -384,6 +411,7 @@ "Dudow", "Dustin", "Paul Duvernay", + "Ebutahapro07tr", "Edson", "Glen Edwards", "Amr Wassiem Eessa", @@ -408,6 +436,7 @@ "EnglandFirst", "enzo", "Erick", + "Erkam", "Jonas Ernst", "NO es", "Shayan Eskandari", @@ -451,6 +480,7 @@ "Robert Fischer", "Kai Fleischmann", "Iancu Florin", + "FLᎧRᏋᏁTIᏁᎧ", "Angelo Fontana", "FortKing", "Golden Freddy", @@ -481,14 +511,17 @@ "gene.mTs", "GeoMatHeo", "GG (9.2)", + "Onkar Ghagarum", "GHAIS", "Omar Ghali", + "GhOsT_St3p", "GhostGamer", "Gian", "Gianfranco", "Gianluca11", "Aldi gibran", "Aidan Gil", + "Noe Marley Ginting", "Giovalli99", "Giovanny", "Dc superhero girl", @@ -526,6 +559,7 @@ "Happaphus", "Hariq", "harojan", + "Harsh", "Abdi Haryadi", "Hasan", "Mohammad hasan", @@ -537,6 +571,7 @@ "Hayate16", "Lukas Heim", "Hugues Heitz", + "HellisWrath", "hellobro", "Christoffer Helmfridsson", "Hemra", @@ -560,6 +595,7 @@ "Robin Hofmann", "hola", "Sebasian Varela Holguin", + "Holystone", "Jeremy Horbul", "Hosein", "hoseinا", @@ -579,6 +615,7 @@ "Igor", "IL_SERGIO", "!YamGila (Syed Ilham)", + "Iliya_bomB", "illonis", "Ily77788", "Ilya", @@ -588,7 +625,9 @@ "IND_PIYUSH", "Indecisive", "indieGEARgames", + "Darkness indo", "Indohuman", + "IniSaya6666", "Anestis Ioakimidis", "Dragomir Ioan", "Isa", @@ -606,6 +645,7 @@ "Aleksandar Janic", "Martin Jansson", "JasimGamer", + "Jason", "Jbo", "JCIBravo", "Jd", @@ -615,9 +655,11 @@ "CrackerKSR (Kishor Jena)", "CrackerKSR (Kishor Jena))", "Jeroen", + "jesus", "Jetty", "Jeulis", "Jewellbenj", + "jgst2007@gmail.com", "Zhou Jianchu", "jimmy", "Jiren", @@ -659,6 +701,7 @@ "Kalyan", "Kamal", "Kamil (Limak09)", + "Kaneki", "Smurfit Kappa", "Mustafa Karabacak", "karabin", @@ -721,6 +764,7 @@ "Jan Kölling", "L_JK", "John Patrick Lachica", + "laikrai", "m a lakum", "K. Larsen", "Nicklas Larsen", @@ -729,6 +773,7 @@ "Lazered", "Lazydog", "Elia Lazzari", + "이지민 (Ji-Min Lee)", "Mick Lemmens", "Leo", "Lester", @@ -744,6 +789,7 @@ "lin", "Dustin Lin", "Kyle Lin", + "Linux44313", "LiteBalt", "LittleNyanCat", "Lkham", @@ -799,6 +845,7 @@ "Marchella", "Marcin", "Marco", + "Filip Marek", "Marcin Marek", "Mariel", "Marin", @@ -824,10 +871,12 @@ "Medic别闹我有药", "German Medin", "Martin Medina", + "Mehret Mehanzel", "Mehrdad", "Kevin Mejía", "MereCrack", "Mert", + "Meysam", "MGH", "Mick", "Miguel", @@ -850,9 +899,11 @@ "mobin", "Moh", "Mohamadali", + "Mohamadamin", "Mohamed", "Mohammad", "Mohammad11dembele", + "Mohammadhosain", "Mohammed", "1n Mohhaamad", "MONIRIE", @@ -870,6 +921,7 @@ "Mrmaxmeier", "MrNexis", "MrS0meone", + "Ivan Ms", "Msta", "Muhammed Muhsin", "MujtabaFR", @@ -884,17 +936,20 @@ "Luca Müller", "nacho", "Nagaarjun(pongal)", + "Nahuelgomez1607", "Nasser", "Natasja", "Nathan", "naveentamizhan123456", "Nayan", + "Nazar", "Nazar_1232", "Behnam Nazemi", "nazroy", "Ndrio°o", "NecroMeerkat", "Neel", + "Nel", "Nemeil", "Mattia Nepote", "Gabriel Del Nero", @@ -924,6 +979,7 @@ "Noobslaya101", "noorjandle1", "Petter Nordlander", + "NotBrojasAgain", "Ntinakos555", "NullWizard", "Dhimas Wildan Nz", @@ -946,19 +1002,24 @@ "PangpondTH", "PantheRoP", "Gavin Park", + "Parkurist", "Pastis69", "Sagar patil", "pato", "patrick", "paulo", "Dominik Pavešić", + "BARLAS PAVLOS-IASON", "PC189085", "PC192082", "pc192089", "PC261133", + "PC295933", + "pebikristia", "Pedro", "Jiren/Juan Pedro", "Peque", + "Rode Liliana Miranda Pereira", "Jura Perić", "Panumas Perkpin", "Pero", @@ -976,6 +1037,7 @@ "Danilo \"Logan\" Pirrone", "PivotStickfigure12", "Pixelcube", + "PixelStudio", "pixil", "PizzaSlayer64", "Elian Pj", @@ -1029,6 +1091,7 @@ "Razil", "Jaiden Razo", "RCSV159", + "Re", "realSamy", "REDEJCR", "redyan", @@ -1037,6 +1100,7 @@ "releaseHUN", "renas", "Renārs", + "Repressive20", "Devair Restani", "RetroB", "Torsten Reuters", @@ -1081,6 +1145,7 @@ "Dosta Rumson", "Hong Ruoyong", "Philip Ruppert", + "Ryan", "LiÇViN:Cviatkoú Kanstançin Rygoravič", "Ricky Joe S.Flores", "Rami Sabbagh", @@ -1121,7 +1186,9 @@ "ShockedGaming", "Shayan Shokry", "Dominik Sikora", + "Leonardo Henrique da Silva", "Sebastian Silva", + "Simotoring", "Skick", "sks", "Max Sky", @@ -1156,6 +1223,7 @@ "Stephanie", "stephen", "Janis Stolzenwald", + "Storm", "SYED EPIC STUDIOS", "sun.4810", "Samet Sunal", @@ -1168,6 +1236,7 @@ "Jorge Luis Sánchez", "Daniel Sýkora", "Arung Taftazani", + "taha", "Juancho Talarga", "Emre Talha(Alienus)", "talopl123", @@ -1178,6 +1247,7 @@ "Tarma", "tarun", "Tauras", + "tcnuhgv", "tdho", "Teals53", "Teapoth", @@ -1188,12 +1258,14 @@ "Marcel Teleznob", "TempVolcano3200", "Yan Teryokhin", + "TestGame1", "testwindows8189", "tgd4", "Than", "Thanakorn7215", "thatFlaviooo", "The_Blinded", + "Thebosslol66", "thejoker190101", "TheLLage", "TheMikirog", @@ -1252,6 +1324,7 @@ "vinicius", "Robin Vinith", "vinoth", + "Vishal", "VTOR", "Fernando Véliz", "Vít", @@ -1292,6 +1365,7 @@ "Ajeet yadav", "yahya", "Yamir", + "YannSonic", "Yantohrmnt401", "amr yasser", "YellowTractor", @@ -1341,6 +1415,7 @@ "Štěpán", "Cristian Țicu", "Μπαρλάς Παύλος-Ιάσονας", + "Ανέστης Πλήθος", "Роман Абрамо", "Роман Абрамов", "Андрей (Krays)", @@ -1382,6 +1457,8 @@ "اا", "احمد اسامه", "احمد سني اسماعيل", + "الأول", + "مُحمَّد الأول", "البطل", "بسام البطل", "ابو العواصف2020", @@ -1405,12 +1482,15 @@ "محمد حسن عزیزی", "علی", "سيد عمر", + "عيسى", "اللهم صل على محمد وآل محمد", "امیر محمد", + "هادی مرادی", "سعید مهجوری", "مهدی", "سید احمد موسوی", "عادل ن.", + "نریمان", "عادل نوروزی", "ه۶۹", "انا يا عمر انا بران يا عمر انا بران يا عمر انا بران يا عمر انا بران يا عمر انا بران يا عمر انا بران يا عمر انا بران يا عمر انا بران يا عمر انا بران يا عمر انا بران يا عمر انا بران يا", @@ -1431,6 +1511,7 @@ "别闹我有药", "别闹我有药/Medic", "别闹我有药Medic", + "南宫銷子()", "夏神(后期汉化修正)", "小黑猫", "张帅", @@ -1441,9 +1522,12 @@ "枫夜", "毛毛毛大毛", "熊老三", + "盐焗汽水er", "神仙", + "蔚蓝枫叶", "鲲鹏元帅", "꧁ℤephyro꧂", + "공팔이", "권찬근", "김원재", "넌", diff --git a/dist/ba_data/data/languages/arabic.json b/dist/ba_data/data/languages/arabic.json index e40272f..db5e42d 100644 --- a/dist/ba_data/data/languages/arabic.json +++ b/dist/ba_data/data/languages/arabic.json @@ -1,37 +1,37 @@ { "accountSettingsWindow": { - "accountNameRules": "لا يمكن لأسماء الحِسابَات ان تحتوي على إيموجي أو حروف خاصة", + "accountNameRules": "لا يمكن لاسماء الحِسابَات أن تحتوي على رموز تعبيرية أو حروف غير ألفبائية", "accountProfileText": "معلومات اللاعبين", "accountsText": "حسابات", "achievementProgressText": "${TOTAL} من أصل ${COUNT} إنجازاتك: أنجزت", "campaignProgressText": "تقدم الحملة [HARD]:${PROGRESS}", "changeOncePerSeason": "يمكنك تغييره مرة واحدة في الموسم", "changeOncePerSeasonError": "يجب عليك الانتظار حتى الموسم القادم لتغيير هذا مجددا (${NUM} أيام )", - "customName": "الإسم المخصص", - "linkAccountsEnterCodeText": "أدخل الرمز", - "linkAccountsGenerateCodeText": "أنشئ رمز", + "customName": "الاسم المخصص", + "linkAccountsEnterCodeText": "ادخل الرمز", + "linkAccountsGenerateCodeText": "انشئ رمز", "linkAccountsInfoText": "(مشاركة تقدمك مع الاجهزة الاخرى)", - "linkAccountsInstructionsNewText": "لربط حسابين،- انشئ رمز من الجهاز المراد انشاء الحساب فية*\n- ًوقم بأدخال الرمز في جهاز المربوط بة حساب مسبق\n\nالبيانات من الحساب الاول سوف يتم مشاركتها بين الجهازين*\n\n من الحسابات كحد اقصى ${COUNT} يمكنك انشاء*\n\n هام : اربط حسابات غير مستخدمة والتي تكون خاصة بك ومع اصدقاء يمكن الوثوق بهم\n\nلا يمكنك ان تلعب بنفس الحساب في جهازين في انٍ واحد", + "linkAccountsInstructionsNewText": "لربط حسابين،- انشئ رمز من الجهاز المراد انشاء الحساب فيه\n- وقم بإدخال الرمز في الجهاز الآخر\n\nالبيانات من الحساب الأول سوف يتم مشاركتها بين الجهازين\n\n من الحسابات كحد أقصى ${COUNT} يمكنك انشاء\n\n تنويه : فقط اربط الحسابات التي تملكها، إذا ربطت حسابك مع الأصدقاء،\n\n .لن يمكنكما اللعب معًا في نفس الوقت", "linkAccountsInstructionsText": "لربط حسابين, انتج كود على احد الحسابين \nو ادخل هذا الكود على الاخر.\nالتقدم و المخزون سيشتركا.\nيمكنك ربط حتى ${COUNT} حسابات.\n\nكن حذراً; هذا لا يمكن استرجاعه", "linkAccountsText": "ربط حساب", "linkedAccountsText": ": حساباتي المرتبطة", - "nameChangeConfirm": "?${NAME} هل تريد تغير اسم حسابك الى", + "nameChangeConfirm": "هل تريد تغيير اسم حسابك إلى ${NAME}؟", "resetProgressConfirmNoAchievementsText": "سوف يحذف هذا الخيار تقدمك في الحملات التعاونية ولن يحذف تذاكرك\nلا يمكن إلغاء هذا الخيار\nهل أنت متأكد ؟", - "resetProgressConfirmText": ":عند موافقتك على هذا الاخيار سوف يتم\n(حذف انجازاتك وتقدمك الحالي(لكن لن تخسر تَذَاكِرك\nاذا وافقت على هذا القرار لا يمكنك تراجع عنه\nهل أنت متأكد؟", - "resetProgressText": "إمسح تقدمك", - "setAccountName": "حدد إسم للحساب", - "setAccountNameDesc": "..اختر اسم لحسابك\nيمكنك اختيار نفس اسم حساباتك الاخرى\nولاكن يجب ان يكون مختلف قليلاً", - "signInInfoText": "قم بتسجيل دخولك لتجمع بطاقات, وتتحدى الاعبين حول العالم\nو لحفظ ونشر تقدمك عبر الاجهزة", + "resetProgressConfirmText": ":عند موافقتك على هذا الاخيار سوف يتم\n(حذف انجازاتك وتقدمك الحالي(لكن لن تخسر تَذَاكِرك\nإذا وافقت على هذا القرار لا يمكنك تراجع عنه\nهل أنت متأكد؟", + "resetProgressText": "امسح تقدمك", + "setAccountName": "حدد اسم للحساب", + "setAccountNameDesc": "اختر اسم لحسابك\nيمكنك استعمال الاسم من أحد حساباتك المرتبطة\nأو إنشاء اسم فريد.", + "signInInfoText": "،قم بتسجيل دخولك لتجمع بطاقات، وتتحدى اللاعبين حول العالم\n.ولمشاركة تقدمك عبر الأجهزة", "signInText": "تسجيل الدخول", "signInWithDeviceInfoText": "الحساب التلقائي متوفر فقط على هذا الجهاز", "signInWithDeviceText": "سجل دخولك بحساب الجهاز", "signInWithGameCircleText": "Game Circle سجل دخولك بواسطة", - "signInWithGooglePlayText": "Google Play سجل الدخول عبر", - "signInWithTestAccountInfoText": "(نوع حساب ارثي; استخدم حسابات الجهاز متجهه للامام)", - "signInWithTestAccountText": "سجل الدخول مع اختبار الحساب", + "signInWithGooglePlayText": "Google Play سجل دخولك عبر", + "signInWithTestAccountInfoText": "(حساب موجود على هاتفك; استخدم حساب الهاتف للمتابعة)", + "signInWithTestAccountText": "تسجيل الدخول بحساب تجريبي", "signOutText": "تسجيل الخروج", - "signingInText": "...جاري تسجيل دخولك", - "signingOutText": "...جاري تسجيل خروجك", + "signingInText": "...جارٍ تسجيل دخولك", + "signingOutText": "...جارٍ تسجيل خروجك", "testAccountWarningOculusText": "تحذير: انت تقوم بتسجيل الدخول باستخدام حساب تجريبي.\nسيستبدل بحساب حقيقي خلال هذا العام الذي من خلاله\nسوف تقدر على شراء البطاقات ومزايا أخرى.\n\nإلى الان يمكنك الحصول على جميع البطافات في اللعبة.\n(على الرغم من ذلك، قم بالحصول على حساب متقدم مجانا)", "ticketsText": "بطاقاتك الحالية:${COUNT}", "titleText": "الحساب", @@ -44,22 +44,22 @@ "achievementText": "إنجاز", "achievements": { "Boom Goes the Dynamite": { - "description": "TNT اقتل 3 خصوم بأستخدام صندوق", - "descriptionComplete": "TNTتم قتل 3 خصوم بصندوق ال", - "descriptionFull": "${LEVEL} اقتل 3 خصوم بالمتفجِّرات في", - "descriptionFullComplete": "${LEVEL} تم قتل 3 خصوم بالمتفجِّرات في", + "description": "اقتل 3 أشخاص وضيعين باستخدام صندوق المتفجرات", + "descriptionComplete": "تم قتل 3 أشخاص وضيعين باستخدام صندوق متفجرات", + "descriptionFull": "${LEVEL} اقتل 3 وضيعين بالمتفجِّرات في", + "descriptionFullComplete": "${LEVEL} تم قتل 3 وضيعين بالمتفجِّرات في", "name": "انفجار قادم من الديناميت" }, "Boxer": { "description": "فز بدون استخدامك للقنابل", "descriptionComplete": "لقد فزت بدون استخدام القنابل", - "descriptionFull": "قم بإكمال ${LEVEL} بدون أستخدام أي قنابل", - "descriptionFullComplete": "أكمل ${LEVEL} بدون أستخدام أي قنابل", + "descriptionFull": "قم بإكمال ${LEVEL} بدون استخدام أية قنابل", + "descriptionFullComplete": "اكمل ${LEVEL} بدون استخدام أية قنابل", "name": "مُلاكِمْ" }, "Dual Wielding": { - "descriptionFull": "{اتصل بجهازي تحكم عن بعد {جهاز او تطبيق", - "descriptionFullComplete": "{متصل بجهازي تحكم {جهاز او تطبيق", + "descriptionFull": "وصل قطعتي تحكم (بالعتاد أو تطبيق)", + "descriptionFullComplete": "متصل بجهازي تحكم (بالعتاد أو تطبيق)", "name": "اللكمة المزدوجة" }, "Flawless Victory": { @@ -70,7 +70,7 @@ "name": "الفوز المستحق" }, "Free Loader": { - "descriptionFull": "إبدأ بلعب الوضع الحر للجميع مع لاعبين أو أكثر", + "descriptionFull": "ابدأ بلعب الوضع الحر للجميع مع لاعبين أو أكثر", "descriptionFullComplete": "تم بدء لعبة بوضع الحرية للجميع مع لاعِبَيْنْ أو أكثر", "name": "الفريق المجاني" }, @@ -82,11 +82,11 @@ "name": "منقب الذهب" }, "Got the Moves": { - "description": "انتصر بدون استخدام اللكمات او القنابل", + "description": "انتصر بدون استخدام اللكمات أو القنابل", "descriptionComplete": "لقد انتصرت بدون استخدامك للكمات او القنابل", "descriptionFull": "بدون استخدام اللكمات أو القنابل ${LEVEL} فز في", "descriptionFullComplete": "بدون استخدام اللكمات أو القنابل ${LEVEL} لقد ربحت في", - "name": "الاسلحة المخفية" + "name": "الأسلحة المخفية" }, "In Control": { "descriptionFull": "(قم بتوصيل جهاز تحكم (جهاز أو تطبيق", @@ -499,32 +499,32 @@ "createEditPlayerText": "<اصنع او عدل حساب>", "createText": "اصنع", "creditsWindow": { - "additionalAudioArtIdeasText": "صوت إضافي، عمل فني مبكر، وأفكار حسب ${NAME}", - "additionalMusicFromText": "موسيقى إضافية من ${NAME}", - "allMyFamilyText": "جميع اصدقائي وعائلتي التي ساعدتني في لعب الاختبار", - "codingGraphicsAudioText": "الترميز والرسومات والصوت حسب ${NAME}", - "languageTranslationsText": "ترجمة اللغة", - "legalText": "القانونية:", - "publicDomainMusicViaText": "موسيقى النطاق العام عبر ${NAME}", - "softwareBasedOnText": "ويستند هذا البرنامج جزئيا على عمل ${NAME}", - "songCreditText": "${TITLE} يؤديه ${PERFORMER}\nيتكون من ${COMPOSER}، مرتبة حسب ${ARRANGER}، تم النشر بواسطة${PUBLISHER}،\nمن باب المجامله${SOURCE}", - "soundAndMusicText": "الصوت والموسيقى:", - "soundsText": "الاصوات (${SOURCE}):", - "specialThanksText": "شكر خاص", - "thanksEspeciallyToText": "شكرا بشكل خاص على ${NAME}", - "titleText": "${APP_NAME} من المساعدين", - "whoeverInventedCoffeeText": "هو الذي اخترع القهوة" + "additionalAudioArtIdeasText": "الأصوات الإضافية، الأعمال المبتكرة، والأفكار من قبل ${NAME}", + "additionalMusicFromText": "المعازف الإضافية من ${NAME}", + "allMyFamilyText": "كل أصدقائي وعائلتي التي ساعدتني لتجربة اللعبة", + "codingGraphicsAudioText": "البرمجة، والرسومات، والأصوات انشأها ${NAME}", + "languageTranslationsText": "مترجمي اللُّغات", + "legalText": ":الحقوق القانونية", + "publicDomainMusicViaText": "معازف النطاق العام بواسطة ${NAME}", + "softwareBasedOnText": "هذه البرمجيات تعتمد على جزء من عمل ${NAME}", + "songCreditText": "${PERFORMER} تم تأديتها من قبل ${TITLE}\n نشر بواسطة ${PUBLISHER}،توزيع ${ARRANGER}، تم التأليف من قبل ${COMPOSER}\nبتصريح من ${SOURCE}", + "soundAndMusicText": "الأصوات والمعازف:", + "soundsText": "تأثيرات الصوت من: (${SOURCE})", + "specialThanksText": "شكر خاص:", + "thanksEspeciallyToText": "والشكر خاصةً لـ${NAME}", + "titleText": "فريق عمل ${APP_NAME}", + "whoeverInventedCoffeeText": "الشخص الذي اخترع القهوة" }, - "currentStandingText": "وضعك الحالي هو # ${RANK}", - "customizeText": "...تعديل", + "currentStandingText": "تصنيفك الحالي هو #${RANK}", + "customizeText": "تعديل...", "deathsTallyText": "${COUNT} وفيات", "deathsText": "موت", "debugText": "التصحيح", "debugWindow": { "reloadBenchmarkBestResultsText": "ملاحظة: فمن المستحسن أن قمت بتعيين إعدادات-> الرسومات-> القوام إلى 'عالية' أثناء اختبار هذا.", - "runCPUBenchmarkText": "تشغيل وحدة المعالجة المركزية المعيار", - "runGPUBenchmarkText": "تشغيل معيار معالج الرسومات", - "runMediaReloadBenchmarkText": "تشغيل معيار إعادة تحميل الوسائط", + "runCPUBenchmarkText": "قياس أداء المعالج", + "runGPUBenchmarkText": "قياس أداء معالج الرسوميات", + "runMediaReloadBenchmarkText": "قياس أداء وحدة تحميل الوسائط", "runStressTestText": "تشغيل اختبار الإجهاد", "stressTestPlayerCountText": "عدد اللاعبين", "stressTestPlaylistDescriptionText": "اختبار الإجهاد قائمة التشغيل", @@ -540,12 +540,12 @@ "deleteText": "حذف", "demoText": "عرض", "denyText": "رفض", - "desktopResText": "ديسكتوب ريس", + "desktopResText": "جودة سطح المكتب", "difficultyEasyText": "سهل", "difficultyHardOnlyText": "الوضع الصعب فقط", "difficultyHardText": "صعب", "difficultyHardUnlockOnlyText": "لا يمكن فتح هذا المستوى إلا في الوضع الصعب.\n هل تعتقد أن لديك ما يلزم!؟!؟!", - "directBrowserToURLText": "يرجى توجيه متصفح ويب إلى عنوان ورل التالي:", + "directBrowserToURLText": "وجه متصفح الشابكة إلى العنوان التالي:", "disableRemoteAppConnectionsText": "تعطيل اتصالات التطبيق عن بعد", "disableXInputDescriptionText": "يسمح أكثر من 4 وحدات تحكم ولكن قد لا تعمل كذلك.", "disableXInputText": "xinput تعطيل", @@ -565,22 +565,22 @@ "titleText": "قائمة تشغيل محرر" }, "editProfileWindow": { - "accountProfileInfoText": "يحتوي هذا الملف الشخصي الخاص على اسم وأيقونة بناء على حسابك.\n${ICONS} \nقم بإنشاء ملفات تعريف مخصصة لاستخدام أسماء مختلفة أو أيقونات مخصصة.", + "accountProfileInfoText": "يحتوي هذا الملف الشخصي الفريد على اسم\nوأيقونة تعتمد على حسابك.\n\n${ICONS}\n\nانشئ ملف شخصي مخصص لاستعمال\nاسماء مختلفة أو أيقونات مخصصة.", "accountProfileText": "(ملف تعريف الحساب)", "availableText": "الاسم \"${NAME}\" متاح.", - "characterText": "شخصيه", + "characterText": "الشخصية", "checkingAvailabilityText": "جار التحقق من التوفر ل \"${NAME}\" ...", "colorText": "اللون", "getMoreCharactersText": "الحصول على المزيد من الشخصيات ...", "getMoreIconsText": "الحصول على المزيد من الرموز ...", - "globalProfileInfoText": "ملامح اللاعب العالمي مضمونة للحصول على \n أسماء فريدة من نوعها في جميع أنحاء العالم. كما تشمل الرموز المخصصة.", + "globalProfileInfoText": "ملفات اللاعب العالمية مصممة لتملك\nاسم عالمي فريد. وأيضًا تتضمن أيقونات مخصصة", "globalProfileText": "(ملف شخصي عالمي)", "highlightText": "تسليط الضوء", "iconText": "أيقونة", "localProfileInfoText": "ملامح لاعب المحلي ليس لديهم رموز وأسمائهم\nغير مضمونة لتكون فريدة من نوعها. الترقية إلى ملف شخصي عام\nلحجز اسم فريد وإضافة رمز مخصص.", "localProfileText": "(الملف الشخصي المحلي)", "nameDescriptionText": "اسم اللاعب", - "nameText": "الأسم", + "nameText": "الاسم", "randomText": "عشوائي", "titleEditText": "تعديل الملف الشخصي", "titleNewText": "ملف شخصي جديد", @@ -616,9 +616,9 @@ "useMusicFolderText": "مجلد ملفات الموسيقى" }, "editText": "تعديل", - "endText": "نهايه", + "endText": "إنهاء", "enjoyText": "استمتع", - "epicDescriptionFilterText": "${DESCRIPTION} في حركة بطيئة ملحمية.", + "epicDescriptionFilterText": "${DESCRIPTION} بحركة ملحمية بطيئة", "epicNameFilterText": "الملحمي ${NAME}", "errorAccessDeniedText": "تم الرفض", "errorOutOfDiskSpaceText": "انتهت مساحة التخزين", @@ -646,7 +646,7 @@ "fiveKillText": "خمسة قتل !!!", "flawlessWaveText": "موجة لا تشوبه شائبة!", "fourKillText": "قتل رباعي !!!", - "friendScoresUnavailableText": "نقاط الاصدقاء غير متوفره.", + "friendScoresUnavailableText": ".نقاط الأصدقاء غير متوفرة", "gameCenterText": "GameCenter", "gameCircleText": "GameCircle", "gameLeadersText": "لعبة ${COUNT} قادة", @@ -815,43 +815,44 @@ "visualsText": "صور" }, "helpWindow": { - "bombInfoText": "- قنبلة -\nأقوى من اللكمات، ولكن\nيمكن أن يؤدي إلى إصابة خطيرة.\nللحصول على أفضل النتائج، رمي نحو العدو قبل نفاذ الفتيل.", - "canHelpText": "يمكن أن يساعدك ${APP_NAME}.", - "controllersInfoText": "يمكنك تشغيل ${APP_NAME} مع الأصدقاء عبر شبكة، أو أنت\nيمكن أن تلعب جميع على نفس الجهاز إذا كان لديك ما يكفي من وحدات التحكم.\n${APP_NAME} يدعم مجموعة متنوعة منها؛ يمكنك حتى استخدام الهواتف\nكمحكمين عبر تطبيق '${REMOTE_APP_NAME}' المجاني.\nانظر إعدادات-> وحدات تحكم لمزيد من المعلومات.", + "bombInfoText": "القنبلة\nأقوى من اللكمات، لكن من\nالممكن أن تؤدي لإيذاء النفس\nلأفضل النتائج، ارمها\nنحو العدو قبل أن ينفذ الفتيل.", + "canHelpText": "تستطيع مساعدتك ${APP_NAME}.", + "controllersInfoText": "يمكنك لعب ${APP_NAME} مع أصدقائك عبر الشبكة، أو يمكنكم\nجميعًا اللعب على نفس الجهاز إذا كنت تمتلك أذرع تحكم كافية.\n${APP_NAME} تدعم أنواع متعددة من أذرع التحكم؛ حتى الهواتف يمكن استعمالها\nكذراع تحكم من خلال تطبيق ${REMOTE_APP_NAME}.\nلمزيد من المعلومات اذهب للإعدادات>التحكم.", + "controllersInfoTextRemoteOnly": "You can play ${APP_NAME} with friends over a network, or you\n can all play on the same device by using phones as\n controllers via the free '${REMOTE_APP_NAME}' app.", "controllersText": "التحكم", - "controlsSubtitleText": "يحتوي الطابع الصديق ${APP_NAME} على بعض الإجراءات الأساسية:", - "controlsText": "ضوابط", + "controlsSubtitleText": "شخصية ${APP_NAME} الخاصة بك تحتوي على العديد من الخصائص أهمها:", + "controlsText": "وحدات التحكم", "devicesInfoText": "يمكن تشغيل إصدار فر الذي يبلغ ${APP_NAME} عبر الشبكة\nالنسخة العادية، حتى سوط خارج الهواتف الإضافية، وأقراص،\nوأجهزة الكمبيوتر والحصول على اللعبة الخاصة بك على. بل يمكن أن يكون مفيدا ل\nربط نسخة منتظمة من اللعبة إلى الإصدار فر فقط ل\nالسماح للناس خارج لمشاهدة العمل.", "devicesText": "الأجهزة", - "friendsGoodText": "هذه هي جيدة لديك. ${APP_NAME} أكثر متعة مع العديد\nلاعبين ويمكن أن تدعم ما يصل إلى 8 في وقت واحد، الأمر الذي يقودنا إلى:", + "friendsGoodText": "من الرائع أن تحظى بهم. ${APP_NAME} أكثر متعة عندما تلعب مع عدة لاعبين\nواللعبة تدعم اللعب مع 8 لاعبين في وقت واحد، مما يقودنا إلى:", "friendsText": "الاصدقاء", - "jumpInfoText": "- القفز -\nالقفز لعبور الثغرات الصغيرة،\nلرمي الأشياء أعلى، و\nللتعبير عن مشاعر الفرح.", - "orPunchingSomethingText": "أو اللكم شيئا، ورميها من الهاوية، وتفجيرها على الطريق مع قنبلة لزجة.", + "jumpInfoText": "القفز\nقم بالقفز لعبور الحفر الصغيرة،\nولرمي الأشياء أبعد،\nوللتعبير عن مشاعر الفرح.", + "orPunchingSomethingText": ".أو ضرب شيء، ورميه من على الجرف، وتفجيره بالمرة بقنبلة لزجة", "pickUpInfoText": "- امسك -\nالاستيلاء على الأعلام، والأعداء، أو أي شيء\nوإلا لا انسحب على الأرض.\nاضغط مرة أخرى لرمي.", "powerupBombDescriptionText": "يتيح لك سوط من ثلاث قنابل\nفي صف واحد بدلا من واحد فقط.", "powerupBombNameText": "قنابل ثلاثية", - "powerupCurseDescriptionText": "ربما كنت ترغب في تجنب هذه.\n ...او هل انت؟", + "powerupCurseDescriptionText": "أعتقد من الجيد الإبتعاد عن هذا.\nإلا إذا كنت ستقوم بـ..؟", "powerupCurseNameText": "لعنة", - "powerupHealthDescriptionText": "يسترجع صحتك كامله.\nلن تخمن ابدا.", - "powerupHealthNameText": "حزمه متوسطه", - "powerupIceBombsDescriptionText": "اضعف من القنابل العاديه\nولكن تجعل اعدائك مجمدين\nواكثر هشاشه", + "powerupHealthDescriptionText": "يشفيك بشكل كامل.\nكما كأن شيئًا لم يحدث.", + "powerupHealthNameText": "حقيبة إسعاف", + "powerupIceBombsDescriptionText": "أضعف من القنابل العادية\nلكن يُبقي أعدائك مجمدين\nويجعلهم هشين للغاية.", "powerupIceBombsNameText": "قنابل الجليد", - "powerupImpactBombsDescriptionText": "أضعف قليلا من القنابل العادية،\nلكنها تنفجر على التأثير.", - "powerupImpactBombsNameText": "الزناد القنابل", - "powerupLandMinesDescriptionText": "هذه تأتي في حزم من 3؛\nمفيدة للدفاع الأساسي أو\nإيقاف الأعداء السريعة", - "powerupLandMinesNameText": "الالغام-الارضيه", - "powerupPunchDescriptionText": "يجعل لكم اللكمات أصعب،\nأسرع، أفضل، أقوى.", + "powerupImpactBombsDescriptionText": "أضعف قليلًا من القنابل التقليدية\nلكنها تنفجر بمجرد أن تلمس أي شيء.", + "powerupImpactBombsNameText": "قنابل الإستهداف", + "powerupLandMinesDescriptionText": "تأتي هذه الحزمة بثلاث قطع\nمن اللغم الأرضي مفيد للدفاع عن \nالقاعدة، وإيقاف الأعداء العدائين.", + "powerupLandMinesNameText": "ألغام-أرضية", + "powerupPunchDescriptionText": "يجعلك تلكم الأشياء بشكل أعمق،\nأسرع، أفضل، أقوى.", "powerupPunchNameText": "قفازات الملاكمة", - "powerupShieldDescriptionText": "يمتص قليلا من الضرر\nحتى لا تضطر إلى ذلك.", + "powerupShieldDescriptionText": "يحمي جسمك من الضرر\nلكي لا تضطر للتعرض للضرر.", "powerupShieldNameText": "درع الطاقة", "powerupStickyBombsDescriptionText": "امساك و ضرب الشي.\nلا يزال يجعلك سعيدا.", "powerupStickyBombsNameText": "قنابل لاصقة", - "powerupsSubtitleText": "وبطبيعة الحال، لا لعبة كاملة دون قوه خارقه:", - "powerupsText": "قوه خارقه", - "punchInfoText": "-اللكمة-\nاللكمات تعطي ضرراً أكبر\n حسب سرعة حركة يدك،\n لذا إركض و إستدر مثل رجل مجنون.", - "runInfoText": "- الركض -\nامسك أي زر لتشغيله. يعمل مشغلات أو\nأزرار الكتف بشكل جيد إذا كان لديك.\nالجري يحصل لك على أماكن أسرع ولكن يجعل من الصعب تشغيله،\nلذلك احترس من المنحدرات", - "someDaysText": "في بعض الايام تشعر بالرغبة في ضرب شيئ.او تفجير شيئ .", - "titleText": "مساعدة ${APP_NAME}", + "powerupsSubtitleText": "وبلا شك، لا توجد لعبة تكتمل بلا قدرات تعزيزية إضافية:", + "powerupsText": "حزم تعزيزية", + "punchInfoText": "اللكم\nعندما تجري بسرعة\nتعطي اللكمات ضرر أكبر،\nلذا اركض وقم بالدوران كالرجل المجنون.", + "runInfoText": "الركض\nاضغط مطولًا على أي زر أعلاه لتشغيله، أيضًا بإمكانك استعمال الزر الخلفي لذراع التحكم للركض.\nيُمكنك الركض من الوصول للأماكن بشكل أسرع لكنه يصعب الإستدارة، لذا انتبه من المنحدرات.", + "someDaysText": "في بعض الأحيان تشعر وكأنك تريد ضرب شيء ما. تفجير شيء ما.", + "titleText": "${APP_NAME} كيفية لعب", "toGetTheMostText": "للحصول على أقصى استفادة من هذه اللعبة، ستحتاج إلى:", "welcomeText": "مرحبا بك في ${APP_NAME}!" }, @@ -994,12 +995,12 @@ "exitToMenuText": "هل تريد الخروج من القائمة؟", "howToPlayText": "كيف ألعب", "justPlayerText": "(فقط ${NAME})", - "leaveGameText": "أترك اللعبة", - "leavePartyConfirmText": "هل تريد حقا ترك الحفله؟", - "leavePartyText": "ترك الحفله", - "quitText": "اخرج", - "resumeText": "متابعه", - "settingsText": "الاعدادات" + "leaveGameText": "اترك اللعبة", + "leavePartyConfirmText": "هل تريد حقًا مغادرة الحفلة؟", + "leavePartyText": "مغادرة الحفلة", + "quitText": "مغادرة", + "resumeText": "استمرار", + "settingsText": "الإعدادات" }, "makeItSoText": "اجعلها كذلك", "mapSelectGetMoreMapsText": "الحصول على المزيد من الخرائط ...", @@ -1007,22 +1008,22 @@ "mapSelectTitleText": "${GAME} خرائط", "mapText": "خرائط", "maxConnectionsText": "اتصالات مكتمل", - "maxPartySizeText": "اقصي حجم للحفله", + "maxPartySizeText": "أقصى حجم للحفلة", "maxPlayersText": "عدد لاعبين مكتمل", - "modeArcadeText": "وضع الأركيد", - "modeClassicText": "الوضع الكلاسيكي", + "modeArcadeText": "وضع اللهو", + "modeClassicText": "الوضع التقليدي", "modeDemoText": "الوضع التجريبي", - "mostValuablePlayerText": "اكثر قيمه للاعب", + "mostValuablePlayerText": "أفضل لاعب", "mostViolatedPlayerText": "اللاعب الأكثر انتهاكاً", - "mostViolentPlayerText": "معظم لاعب عنيف", + "mostViolentPlayerText": "أعنف اللاعبين", "moveText": "تحرك", "multiKillText": "${COUNT}-قتل!!!", - "multiPlayerCountText": "${COUNT} الاعبين", + "multiPlayerCountText": "${COUNT} لاعب", "mustInviteFriendsText": "ملاحظة: يجب دعوة الأصدقاء في\nلوحة \"${GATHER}\" أو إرفاقها\nوحدات تحكم للعب متعددة.", - "nameBetrayedText": "${NAME} خيانه ${VICTIM}.", + "nameBetrayedText": "${NAME} قام بخيانة ${VICTIM}", "nameDiedText": "${NAME} توفي.", "nameKilledText": "${NAME} قتل ${VICTIM}.", - "nameNotEmptyText": "لا يمكن أن يكون الاسم فارغا!", + "nameNotEmptyText": "لا يمكن أن يكون الاسم فارغًا!", "nameScoresText": "${NAME} نقاط!", "nameSuicideKidFriendlyText": "${NAME} توفي عن طريق الخطأ.", "nameSuicideText": "${NAME} انتحر.", @@ -1050,7 +1051,7 @@ "notSignedInText": "لم تقم بتسجيل الدخول", "nothingIsSelectedErrorText": "لا شئ تم اختياره!", "numberText": "#${NUMBER}", - "offText": "ايقاف", + "offText": "إيقاف", "okText": "حسنا", "onText": "تشغيل", "oneMomentText": "لحظة واحدة...", @@ -1171,32 +1172,32 @@ "searching": "جار البحث عن ألعاب بومبسكاد ...", "searching_caption": "اضغط على اسم لعبة للانضمام إليه.\nتأكد من أنك على نفس شبكة واي فاي مثل اللعبة.", "start": "بداية", - "version_mismatch": "عدم تطابق إصدار.\nتأكد من بومبسكاد و بومبسكاد البعيد\nهي أحدث الإصدارات وحاول مرة أخرى." + "version_mismatch": ".الإصداران لا يتطابقان\nتأكد من أن فرقة القنبلة و فرقة القنبلة للتحكم عن بعد\n.تم تحديثهما لآخر إصدار وحاول مجددًا" }, "removeInGameAdsText": "إلغاء تأمين \"${PRO}\" في المتجر لإزالة الإعلانات داخل اللعبة.", "renameText": "إعادة تسمية", "replayEndText": "نهاية الإعادة", - "replayNameDefaultText": "آخر لعبة الإعادة", + "replayNameDefaultText": "إعادة اللعبة الأخيرة", "replayReadErrorText": "حدث خطأ أثناء قراءة ملف إعادة التشغيل.", "replayRenameWarningText": "إعادة تسمية \"${REPLAY}\" بعد لعبة إذا كنت ترغب في الاحتفاظ بها. وإلا فإنه سيتم الكتابة فوقه.", "replayVersionErrorText": "عذرا، تم إجراء هذا الإعادة في صورة مختلفة\nنسخة من اللعبة ولا يمكن استخدامها.", "replayWatchText": "مشاهدة الإعادة", "replayWriteErrorText": "حدث خطأ أثناء كتابة ملف إعادة التشغيل.", - "replaysText": "الاعادة", + "replaysText": "الإعادة", "reportPlayerExplanationText": "استخدم هذه الرسالة الإلكترونية للإبلاغ عن الغش أو اللغة غير الملائمة أو أي سلوك سيئ آخر.\nيرجى وصف ما يلي:", "reportThisPlayerCheatingText": "غش", - "reportThisPlayerLanguageText": "لغة غير لائقة", - "reportThisPlayerReasonText": "ماذا تريد أن تقدم؟", - "reportThisPlayerText": "تقرير هذا اللاعب", - "requestingText": "طلب ...", - "restartText": "اعادة التشغيل", - "retryText": "اعادة المحاولة", + "reportThisPlayerLanguageText": "كلام مسيء", + "reportThisPlayerReasonText": "عن ماذا تريد أن تُبلغ؟", + "reportThisPlayerText": "الإبلاغ عن هذا اللاعب", + "requestingText": "...طلب", + "restartText": "إعادة التشغيل", + "retryText": "إعادة المحاولة", "revertText": "العودة", - "runText": "جري", + "runText": "ركض", "saveText": "حفظ", "scanScriptsErrorText": "حدث خطأ (أخطاء) في مسح النصوص البرمجية ؛ انظر السجل للحصول على التفاصيل.", "scoreChallengesText": "نقاط التحديات", - "scoreListUnavailableText": "قائمة النقاط غير متاحة.", + "scoreListUnavailableText": ".قائمة النقاط غير متاحة", "scoreText": "نتيجة", "scoreUnits": { "millisecondsText": "ميلي ثانية", @@ -1227,27 +1228,27 @@ "disableThisNotice": "(يمكنك تعطيل هذا الإشعار في الإعدادات المتقدمة)", "enablePackageModsDescriptionText": "(تمكن قدرات التعديل الإضافية ولكن تعطيل شبكة اللعب)", "enablePackageModsText": "تمكين تعديل الحزمة المحلية", - "enterPromoCodeText": "أدخل الكود الترويجي", + "enterPromoCodeText": "ادخل الرمز", "forTestingText": "ملاحظة: هذه القيم هي فقط للاختبار وسيتم فقدانها عند خروج التطبيق.", - "helpTranslateText": "${APP_NAME} الترجمات غير الإنجليزية هي منتدى\nبدعم الجهود. إذا كنت ترغب في المساهمة أو التصحيح\nترجمة، اتبع الرابط أدناه. شكرا مقدما!", - "kickIdlePlayersText": "ركلة اللاعبين الخمول", - "kidFriendlyModeText": "وضع الصديقة للطفل (انخفاض العنف، الخ)", + "helpTranslateText": "هي عبارة عن ترجمة ​${APP_NAME}الترجمات غير الإنجليزية ل\nجماعية، إذا أردت المساهمة أو تصحيح الأخطاء اللغوية والإملائية\n!قم بزيارة الرابط أدناه، وشكرًا لكم مقدمًا", + "kickIdlePlayersText": "طرد اللاعبين غير النشطين", + "kidFriendlyModeText": "وضع الأطفال (يقلل العنف، إلخ)", "languageText": "لغة", - "moddingGuideText": "دليل مودينغ", - "mustRestartText": "يجب إعادة تشغيل اللعبة حتى تصبح نافذة المفعول.", + "moddingGuideText": "دليل التعديلات البرمجية", + "mustRestartText": ".يجب أن تقوم بإعادة تشغيل اللعبة لكي يعمل هذا", "netTestingText": "اختبار الشبكة", "resetText": "إعادة تعيين", "showBombTrajectoriesText": "عرض مسارات القنبلة", - "showPlayerNamesText": "إظهار أسماء اللاعبين", + "showPlayerNamesText": "إظهار اسماء اللاعبين", "showUserModsText": "عرض مجلد التعديل", "titleText": "المتقدمة", "translationEditorButtonText": "${APP_NAME} محرر الترجمة", "translationFetchErrorText": "حالة الترجمة غير متاحة", "translationFetchingStatusText": "جار التحقق من حالة الترجمة ...", - "translationInformMe": "أبلغني عندما تحتاج لغتي التحديثات", - "translationNoUpdateNeededText": "اللغة الحالية هي حتى الآن. محدثه!", - "translationUpdateNeededText": "** اللغة الحالية يحتاج التحديثات !! **", - "vrTestingText": "فر اختبار" + "translationInformMe": "ابلغني عندما تحتاج لغتي للتحديث", + "translationNoUpdateNeededText": "!اللُّغة العربية حتى الآن محدثة، هنيئًا لك", + "translationUpdateNeededText": "** !!اللُّغة الحالية بحاجةٍ إلى تحديث **", + "vrTestingText": "تجربة الواقع الإفتراضي" }, "shareText": "شارك", "sharingText": "مشاركة...", @@ -1258,11 +1259,11 @@ "singlePlayerCountText": "1 لاعب", "soloNameFilterText": "منفردا ${NAME}", "soundtrackTypeNames": { - "CharSelect": "اختار شخصيه", + "CharSelect": "اختر شخصية", "Chosen One": "المختار", - "Epic": "وضع الالعاب ملحمه", + "Epic": "وضع اللعب الملحمي", "Epic Race": "سباق ملحمي", - "FlagCatcher": "أمسك العلم", + "FlagCatcher": "امسك العلم", "Flying": "أفكار سعيدة", "Football": "كرة القدم", "ForwardMarch": "الاعتداءات", @@ -1275,22 +1276,22 @@ "Race": "سباق", "Scary": "ملك التل", "Scores": "شاشة النتيجة", - "Survival": "إزالة", + "Survival": "الإقصاء", "ToTheDeath": "مباراة الموت", - "Victory": "شاشه النتيجه النهائيه" + "Victory": "شاشة النتيجة النهائية" }, - "spaceKeyText": "الفراغ", + "spaceKeyText": "مسافة", "statsText": "النتائج", "storagePermissionAccessText": "وهذا يتطلب الوصول إلى التخزين", "store": { - "alreadyOwnText": "أنت تملك بالفعل ${NAME}!", - "bombSquadProNameText": "${APP_NAME} برو", - "bombSquadProNewDescriptionText": "• يزيل الإعلانات في اللعبة والشاشات تذمر\n• يفتح المزيد من إعدادات اللعبة\n• تحتوي ايضا:", + "alreadyOwnText": "!${NAME}أنت بالفعل تملك", + "bombSquadProNameText": "للمحترفين ${APP_NAME}", + "bombSquadProNewDescriptionText": "يزيل الإعلانات في اللعبة والشاشات المزعجة •\nيفتح المزيد من إعدادات اللعبة •\n:يتضمن هذا العرض أيضًا •", "buyText": "شراء", "charactersText": "الشخصيات", "comingSoonText": "قريبا...", "extrasText": "إضافات", - "freeBombSquadProText": "بومبسكاد هو الآن مجانا، ولكن منذ كنت أصلا اشتريت أنت\nوتلقي ترقية بومبسكاد برو و ${COUNT} تذاكر كما شكر لك.\nتتمتع الميزات الجديدة، وشكرا لكم على دعمكم!\nاريك", + "freeBombSquadProText": "فرقة القنبلة الآن أصبحت مجانية، لكن بما أنك اشتريتها\nبطاقات كشكر لك ​${COUNT} ستتلقى فرقة القنبلة القنبلة للمحترفين و\n!استمتع بالميزات الجديدة، وشكرًا لدعمك\n-إيريك", "holidaySpecialText": "عطلة خاصة", "howToSwitchCharactersText": "(انتقل إلى \"${SETTINGS} -> ${PLAYER_PROFILES}\" لتعيين وتخصيص الأحرف)", "howToUseIconsText": "(إنشاء ملفات تعريف لاعب العالمية (في إطار الحساب) لاستخدام هذه)", @@ -1312,18 +1313,18 @@ "searchText": "بحث", "teamsFreeForAllGamesText": "فرق / مجانا للجميع الألعاب", "totalWorthText": "*** ${TOTAL_WORTH} قيمة! ***", - "upgradeQuestionText": "?ترقيه", - "winterSpecialText": "الشتاء خاص", + "upgradeQuestionText": "ترقية؟", + "winterSpecialText": "عرض الشتاء", "youOwnThisText": "- انت تملك هذا -" }, "storeDescriptionText": "8 لاعب حفله لعبة الجنون!\n\nتفجير أصدقائك (أو الكمبيوتر) في البطولة من الألعاب المصغرة المتفجرة مثل القبض على العلم، منفذها هوكي، وملحمة بطيئة الحركة الموت الموت!\n\nضوابط بسيطة ودعم وحدة تحكم واسعة تجعل من السهل لمدة تصل إلى 8 أشخاص للحصول على في العمل. يمكنك حتى استخدام الأجهزة النقالة الخاصة بك عن طريق التحكم عن طريق الحرة 'بومبسكاد البعيد' التطبيق!\n\nالقنابل بعيدا!\n\nتحقق من www.froemling.net/bombsquad لمزيد من المعلومات.", "storeDescriptions": { - "blowUpYourFriendsText": "تفجير أصدقائك.", + "blowUpYourFriendsText": ".فجر أصدقائك", "competeInMiniGamesText": "تنافس في الألعاب المصغرة بدءا من السباق للطيران.", "customize2Text": "تخصيص الشخصيات، الألعاب المصغرة، وحتى الموسيقى التصويرية.", "customizeText": "تخصيص الشخصيات وإنشاء قوائم التشغيل الخاصة بك لعبة صغيرة.", "sportsMoreFunText": "الرياضة أكثر متعة مع المتفجرات.", - "teamUpAgainstComputerText": "فريق ضد الكمبيوتر." + "teamUpAgainstComputerText": ".قم بالتعاون كفريق ضد الحاسوب" }, "storeText": "متجر", "submitText": "ارسال", @@ -1346,7 +1347,7 @@ "timeSuffixSecondsText": "${COUNT}ث", "tipText": "تلميح", "titleText": "فرقة القنبلة", - "titleVRText": "فرقة القنبلة فر", + "titleVRText": "فرقة القنبلة وا", "topFriendsText": "أفضل الأصدقاء", "tournamentCheckingStateText": "التحقق من حالة البطولة. أرجو الإنتظار...", "tournamentEndedText": "انتهت هذه البطولة. وسوف تبدأ واحدة جديدة قريبا.", @@ -1358,32 +1359,32 @@ "tournamentsText": "البطولات", "translations": { "characterNames": { - "Agent Johnson": "وكيل جونسون", - "B-9000": "بي-9000", - "Bernard": "الدب برنارد", + "Agent Johnson": "العميل جونسون", + "B-9000": "الآلي الخارق", + "Bernard": "الدب القطبي", "Bones": "هيكل عظمي", "Butch": "بوتش", - "Easter Bunny": "أرنب عيد الفصح", + "Easter Bunny": "أرنوب", "Flopsy": "فلوبسي", - "Frosty": "رجل ثلج", + "Frosty": "مُقاتل ثلجي", "Gretel": "جريتل", - "Grumbledorf": "Grumbledorf", - "Jack Morgan": "جاك مرجان", - "Kronk": "كرونك", + "Grumbledorf": "المشعوذ", + "Jack Morgan": "خير الدين بارباروسا", + "Kronk": "عدنان", "Lee": "لي", "Lucky": "سعيد الحظ", - "Mel": "ميل", + "Mel": "الطباخ", "Middle-Man": "الرجل المتوسط", "Minimus": "أدنى لا", - "Pascal": "بطريق", - "Pixel": "الفراشه", + "Pascal": "البطريق الكبير", + "Pixel": "حسناء", "Sammy Slam": "سامي سلام", - "Santa Claus": "سانتا كلوس", - "Snake Shadow": "ظل الافعى", + "Santa Claus": "الشيخ", + "Snake Shadow": "مُحارب في الصحراء", "Spaz": "Spaz", "Taobao Mascot": "التميمه تاوباو", "Todd McBurton": "تود بيرتون", - "Zoe": "زوي", + "Zoe": "ليلى", "Zola": "زولا" }, "coopLevelNames": { @@ -1394,29 +1395,29 @@ "Onslaught Training": "التدريب هجمة", "Pro ${GAME}": "برو ${GAME}", "Pro Football": "كرة القدم الإحترافية", - "Pro Onslaught": "هجمه الاحترافيه", - "Pro Runaround": "يركض حول الاحترافيه", - "Rookie ${GAME}": "الصاعد ${GAME}", - "Rookie Football": "الصاعد كرة القدم", - "Rookie Onslaught": "هجمه الصاعد", - "The Last Stand": "الموقف الأخير", - "Uber ${GAME}": "اوبر ${GAME}", - "Uber Football": "اوبر لكرة القدم", - "Uber Onslaught": "اوبر الهجمة", - "Uber Runaround": "الجري حول: وضع صعوبة الاوبر" + "Pro Onslaught": "انقضاض احترافي", + "Pro Runaround": "جولة هروب احترافية", + "Rookie ${GAME}": "${GAME} للفراخ", + "Rookie Football": "فرخ كرة القدم", + "Rookie Onslaught": "انقضاض الفرخ", + "The Last Stand": "آخر من يقف", + "Uber ${GAME}": "${GAME} أوبر", + "Uber Football": "كرة القدم الغزيرة", + "Uber Onslaught": "هجمة غزيرة", + "Uber Runaround": "جولة جري غزيرة" }, "gameDescriptions": { "Be the chosen one for a length of time to win.\nKill the chosen one to become it.": "يكون اختيار واحد لفترة من الوقت للفوز.\nقتل اختيار واحد لتصبح عليه.", - "Bomb as many targets as you can.": "قنبلة العديد من الأهداف ما تستطيع.", + "Bomb as many targets as you can.": "فجر أكبر عدد من الأهداف على قدر استطاعتك.", "Carry the flag for ${ARG1} seconds.": "حمل العلم مقابل ${ARG1} ثانية.", "Carry the flag for a set length of time.": "احمل العلم لمدة محددة من الزمن", "Crush ${ARG1} of your enemies.": "سحق ${ARG1} من أعدائك.", "Defeat all enemies.": "هزيمة جميع الأعداء.", - "Dodge the falling bombs.": "دودج القنابل السقوط.", - "Final glorious epic slow motion battle to the death.": "النهائي المجيدة ملحمة حركة بطيئة معركة حتى الموت.", + "Dodge the falling bombs.": "تفادى القنابل.", + "Final glorious epic slow motion battle to the death.": "آخر معركة ملحمية بطيئة حتى الموت.", "Gather eggs!": "جمع البيض!", "Get the flag to the enemy end zone.": "الحصول على العلم إلى المنطقة نهاية العدو.", - "How fast can you defeat the ninjas?": "هل يمكنك هزيمه النينجا باسرع وقت ممكن؟", + "How fast can you defeat the ninjas?": "كم من الوقت ستحتاج لهزيمة النينجا؟", "Kill a set number of enemies to win.": "قتل عدد معين من الأعداء للفوز.", "Last one standing wins.": "آخر واحد يبقى يفوز.", "Last remaining alive wins.": "آخر شخص يبقى حياً يفوز", @@ -1445,7 +1446,7 @@ "Touch the enemy flag.": "المس علم العدو.", "carry the flag for ${ARG1} seconds": "ثانية ${ARG1} احمل العلم لمدة", "kill ${ARG1} enemies": "اعداء ${ARG1} اقتل", - "last one standing wins": "آخر واحد يقف يفوز", + "last one standing wins": "آخر من يقف يفوز", "last team standing wins": "آخر فريق يتبقى يفوز", "return ${ARG1} flags": "ارجاع ${ARG1} الاعلام", "return 1 flag": "ارجاع 1 الاعلام", @@ -1466,7 +1467,7 @@ "Chosen One": "المختار", "Conquest": "غزو", "Death Match": "مباراة الموت", - "Easter Egg Hunt": "بيضة عيد الفصح هانت", + "Easter Egg Hunt": "تم اصطياد بيضة فصح", "Elimination": "إزالة", "Football": "كرة القدم", "Hockey": "الهوكي", @@ -1485,7 +1486,7 @@ "Keyboard P2": "لوحة المفاتيح P2" }, "languages": { - "Arabic": "عربى", + "Arabic": "العربية", "Belarussian": "البيلاروسية", "Chinese": "الصينية المبسطة", "ChineseTraditional": "التقليدية الصينية", @@ -1513,8 +1514,9 @@ "Russian": "الروسية", "Serbian": "الصربية", "Slovak": "السلوفاكية", - "Spanish": "الأسبانية", + "Spanish": "الإسبانية", "Swedish": "اللغة السويدية", + "Thai": "تايلاندي", "Turkish": "اللغة التركية", "Ukrainian": "الأوكراني", "Venetian": "فينيسي", @@ -1563,6 +1565,7 @@ "Account linking successful!": "تم ربط الحساب بنجاح!", "Account unlinking successful!": "تم إلغاء ربط الحساب بنجاح!", "Accounts are already linked.": "الحسابات مرتبطة بالفعل.", + "Ad view could not be verified.\nPlease be sure you are running an official and up-to-date version of the game.": "يمكن ان يكون الاعلان غير متحقق منه.\nمن فضلك تحقق بأنك بالفعل في نسخة رسمية و محذثة من اللعبة.", "An error has occurred; (${ERROR})": "حدثت مشكلة; (${ERROR})", "An error has occurred; please contact support. (${ERROR})": "حدثت مشكلة; برجاء التواصل مع الدعم. (${ERROR})", "An error has occurred; please contact support@froemling.net.": "حدث خطأ؛ يرجى الاتصال support@froemling.net.", @@ -1657,7 +1660,7 @@ "No Mines": "لا مناجم", "None": "لا شيء", "Normal": "عادي", - "Pro Mode": "وضع برو", + "Pro Mode": "وضع المحترف", "Respawn Times": "أوقات الفجر", "Score to Win": "يسجل الفوز", "Short": "قصيرة", diff --git a/dist/ba_data/data/languages/belarussian.json b/dist/ba_data/data/languages/belarussian.json index 77b7b2b..36846ba 100644 --- a/dist/ba_data/data/languages/belarussian.json +++ b/dist/ba_data/data/languages/belarussian.json @@ -683,11 +683,18 @@ "bluetoothJoinText": "Далучыцца", "bluetoothText": "Bluetooth", "checkingText": "праверка...", + "copyCodeConfirmText": "Код скапіяваны ў буфер абмену", + "copyCodeText": "Скапіяваць код", "dedicatedServerInfoText": "Для дасягнення найлепшых вынікаў наладзьце спецыяльны сервер. Гл. Bombsquadgame.com/server, каб даведацца, як.", "disconnectClientsText": "Гэта адлучыць ${COUNT} гульцоў з вашага\nлоббі. Вы ўпэўнены?", "earnTicketsForRecommendingAmountText": "Сябры атрымаюць ${COUNT} квіткоў, калі яны паспрабуюць гульню\n(вы таксама атрымаеце ${YOU_COUNT} квіткоў за кожнага сябра)", "earnTicketsForRecommendingText": "Падзяліцеся гульнёй, \nкаб атрымаць квіткі.", "emailItText": "Паслаць", + "favoritesSaveText": "Захаваць як абранае", + "favoritesText": "Абранае", + "freeCloudServerAvailableMinutesText": "Наступны бясплатны воблачны сервер будзе абноўлены праз ${MINUTES} мінут", + "freeCloudServerAvailableNowText": "Бясплатны воблачны сервер абнавіўся!", + "freeCloudServerNotAvailableText": "Бясплатных воблачных сервераў няма.", "friendHasSentPromoCodeText": "${COUNT} квіткоў ${APP_NAME} ад ${NAME}", "friendPromoCodeAwardText": "Вы атрымаеце ${COUNT} квіткоў кожны раз, калі ён будзе выкарыстаны.", "friendPromoCodeExpireText": "Код дзейнічае ${EXPIRE_HOURS} гадзін(ы) і працуе толькі для новых гульцоў.", @@ -703,19 +710,21 @@ "googlePlaySeeInvitesText": "Паглядець запрашэнні", "googlePlayText": "Google Play", "googlePlayVersionOnlyText": "(Android / версія Google Play)", - "hostPublicPartyDescriptionText": "Прымае грамадскую вечарыну:", + "hostPublicPartyDescriptionText": "Прымае грамадскую вечарыну", + "hostingUnavailableText": "Хостынг недаступны", "inDevelopmentWarningText": "Увага:\n\nГульня па сетцы - новая функцыя, якая зараз \nразвіваецца. На сённяшні дзень рэкамендуецца, \nкаб усе гульцы знаходзіліся ў адной WiFi сетцы.", "internetText": "Інтэрнэт", "inviteAFriendText": "У сяброў няма гульні? Запрасіце іх паспрабаваць,\nі яны атрымаюць ${COUNT} дадатковых квіткоў.", "inviteFriendsText": "Запрасіць Сяброў", - "joinPublicPartyDescriptionText": "Далучайцеся да грамадскай вечарыны:", - "localNetworkDescriptionText": "Далучыцеся да лоббі ў вашай сетцы:", + "joinPublicPartyDescriptionText": "Далучайцеся да грамадскай вечарыны", + "localNetworkDescriptionText": "Далучайцеся да вечарыны побач (LAN, Bluetooth і г.д.)", "localNetworkText": "Лакальная сетка", "makePartyPrivateText": "Зрабіць Маё Лоббі Прыватным", "makePartyPublicText": "Зрабіце маю партыю публічнай", "manualAddressText": "Адрас", "manualConnectText": "Далучыцца", "manualDescriptionText": "Далучыцеся да лоббі па адрасе:", + "manualJoinSectionText": "Далучыцца по адрасу", "manualJoinableFromInternetText": "Да вас можна далучыцца праз інтэрнэт?:", "manualJoinableNoWithAsteriskText": "Не*", "manualJoinableYesText": "Так", @@ -723,14 +732,17 @@ "manualText": "Ручны", "manualYourAddressFromInternetText": "Ваш адрас з інтэрнэту:", "manualYourLocalAddressText": "Ваш лакальны адрас:", + "nearbyText": "Побач", "noConnectionText": "<няма злучэння>", "otherVersionsText": "(іншыя версіі)", + "partyCodeText": "Код вечарыны", "partyInviteAcceptText": "Згадзіцца", "partyInviteDeclineText": "Адмовіцца", "partyInviteGooglePlayExtraText": "(зайдзіце ў укладку \"Google Play\" у раздзеле \"Сабраць\")", "partyInviteIgnoreText": "Ігнараваць", "partyInviteText": "${NAME} запрасіў\nвас у сваё лоббі!", "partyNameText": "Назва Лоббі", + "partyServerRunningText": "Ваш сервер працуе", "partySizeText": "Размер Лоббі", "partyStatusCheckingText": "Правяраем статус...", "partyStatusJoinableText": "Зараз твае Лоббі дасягаемае праз інтэрнэт", @@ -739,11 +751,21 @@ "partyStatusNotPublicText": "Твае Лоббі не публічнае", "pingText": "Пінг", "portText": "Порт", + "privatePartyCloudDescriptionText": "Прыватныя вечарыны працуюць на выдзеленых воблачных серверах; канфігурацыя маршрутызатара не патрабуецца.", + "privatePartyHostText": "Арганізаваць прыватную вечарыну", + "privatePartyJoinText": "Далучыцца да прыватнай вечарыны", + "privateText": "Прыватны", + "publicHostRouterConfigText": "Для гэтага можа спатрэбіцца наладка перанакіравання порта на вашым маршрутызатары. Для больш простага варыянту арганізаваць прыватную вечарыну.", + "publicText": "Публічны", "requestingAPromoCodeText": "Запыт кода...", "sendDirectInvitesText": "Даслаць Запрашэнні", "sendThisToAFriendText": "Адпраўце гэты код вашаму сябру:", "shareThisCodeWithFriendsText": "Падзяліцца кодам з сябрамі:", "showMyAddressText": "Паказаць мой адрас", + "startHostingPaidText": "Арганізаваць зараз за ${COST}", + "startHostingText": "Арганізаваць", + "startStopHostingMinutesText": "Вы можаце пачаць і спыніць хостынг бясплатна на працягу наступных ${MINUTES} мінут.", + "stopHostingText": "Спыніць хостынг", "titleText": "Сабраць", "wifiDirectDescriptionBottomText": "Калі ўсе прылады падтрымліваюць 'Wi-Fi Direct', яны могуць карыстацца ім, каб падключыцца\nадзін да другога. Калі ўсе прылады падключаны, вы можаце ствараць лоббі, карыстаючыся\nўкладкай \"Лакальная сетка\" так жа, як і з звычайнай WiFi сеткай.\n\nДля лепшых вынікаў хост Wi-Fi Direct павінен таксама быць хостам гульні ${APP_NAME}.", "wifiDirectDescriptionTopText": "Wi-Fi Direct можа выкарыстоўвацца для злучэння Android прылад непасрэдна,\nбез WiFi сеткі. Гэта працуе лепш на Android 4.2 ці навей.\n\nКаб cкарыстацца гэтым, адчыніце налады і знайдзіце 'Wi-Fi Direct'.", @@ -801,6 +823,7 @@ "bombInfoText": "- Бомба - \nМацней за ўдары, але можа нанесці\nшкоду і вам самім. Для лепшых\nвынікаў кідайце ў ворага, пакуль\nне згарэў кнот.", "canHelpText": "${APP_NAME} можа дапамагчы.", "controllersInfoText": "Вы можаце гуляць у ${APP_NAME} з сябрамі праз сетку або, калі\nвы маеце дастаткова кантролераў, на адной прыладзе.\n${APP_NAME} падтрымлівае мноства кантролераў - нават тэлефон \n(для гэтага спатрэбіцца прыкладанне '${REMOTE_APP_NAME}').\nГл. Налады -> Кантролеры для атрымання дадатковай інфармацыі.", + "controllersInfoTextRemoteOnly": "Вы можаце гуляць у ${APP_NAME} з сябраміпа сетцы,альбо вы\nможаце гуляць на адной прыладзе,выкарыстоўваючы тэлефоны ў якасц\nантролераў з дапамогай бясплатнай праграмы '${REMOTE_APP_NAME}'.", "controllersText": "Кантролеры", "controlsSubtitleText": "Ваш персанаж ${APP_NAME} валодае некалькімі базавымі прыёмамі:", "controlsText": "Прыёмы", @@ -1039,6 +1062,7 @@ "offText": "Выключана", "okText": "Так", "onText": "Уключана", + "oneMomentText": "Адну мінуту...", "onslaughtRespawnText": "${PLAYER} з'явіцца ў ${WAVE} хвалі", "orText": "${A} ці ${B}", "otherText": "Іншае...", @@ -1085,6 +1109,7 @@ "playerText": "Гулец", "playlistNoValidGamesErrorText": "У гэтым плэйлісце няма адкрытых гульняў.", "playlistNotFoundText": "плэйліст не знойдзены", + "playlistText": "Плэйліст", "playlistsText": "Плэйлісты", "pleaseRateText": "Калі вам падабаецца ${APP_NAME}, калі ласка, знайдзіце\nчас, каб ацаніць яго ці напісаць водгук. Гэта забя-\nспечвае сувязь і дапамагае развіццю гульні.\n\nДзякуй!\n-Эрык", "pleaseWaitText": "Калі ласка пачакай...", @@ -1506,6 +1531,7 @@ "Slovak": "Славацкая", "Spanish": "Гішпанская", "Swedish": "Шведская", + "Thai": "Тайская мова", "Turkish": "Турэцкі", "Ukrainian": "Украінскі", "Venetian": "Венецыянскі", @@ -1554,6 +1580,7 @@ "Account linking successful!": "Злучэнне акаўнтаў выканана!", "Account unlinking successful!": "Ўліковы запіс паспяхова адключаны!", "Accounts are already linked.": "Акаўнты ўжо злучаны.", + "Ad view could not be verified.\nPlease be sure you are running an official and up-to-date version of the game.": "Немагчыма праверыць прагляд рэкламы.\nПераканайцеся, што вы выкарыстоўваеце афіцыйную і свежую версію гульні.", "An error has occurred; (${ERROR})": "Адбылася памылка; (${ERROR})", "An error has occurred; please contact support. (${ERROR})": "Адбылася памылка; калі ласка, звяжыцеся са службай падтрымкі. (${ERROR})", "An error has occurred; please contact support@froemling.net.": "Узнікла памылка; звяжыцеся з support@froemling.net.", @@ -1579,6 +1606,7 @@ "Max number of profiles reached.": "Максімальная колькасць профіляў дасягнута.", "Maximum friend code rewards reached.": "Дасягнута максімальная ўзнагарода за код сябра.", "Message is too long.": "Паведамленне занадта доўгае.", + "No servers are available. Please try again soon.": "Няма даступных сервераў. Калі ласка, паспрабуйце яшчэ раз пазней.", "Profile \"${NAME}\" upgraded successfully.": "Профіль \"${NAME}\" палепшаны паспяхова.", "Profile could not be upgraded.": "Профіль нельга палепшыць.", "Purchase successful!": "Аб'ект набыты паспяхова!", @@ -1588,10 +1616,12 @@ "Sorry, this code has already been used.": "Прабачце, гэты код ужо выкарыстоўваўся.", "Sorry, this code has expired.": "На жаль, срок дзеяння гэтага кода ўжо скончыўся.", "Sorry, this code only works for new accounts.": "Прабачце, гэты код працуе толькі на новых акаўнтах.", + "Still searching for nearby servers; please try again soon.": "Працягваецца пошук бліжэйшых сервераў; калі ласка, паспрабуйце яшчэ раз пазней.", "Temporarily unavailable; please try again later.": "Часова недаступны; калі ласка паспрабуйце зноў пазней.", "The tournament ended before you finished.": "Турнір скончыўся перад тым, як вы закончылі.", "This account cannot be unlinked for ${NUM} days.": "Немагчыма адлучыць гэты ўліковы запіс на працягу ${NUM} дзён.", "This code cannot be used on the account that created it.": "Кодам нельга скарыстацца на акаўнце, які стварыў яго.", + "This is currently unavailable; please try again later.": "У цяперашні час гэта недаступна; калі ласка, паспрабуйце зноў пазней.", "This requires version ${VERSION} or newer.": "Неабходна версія ${VERSION} гульні ці навей.", "Tournaments disabled due to rooted device.": "Турніры адключаны з-за рутiраванай прылады.", "Tournaments require ${VERSION} or newer": "Для турніраў патрабуецца ${VERSION} або больш позняя версія", diff --git a/dist/ba_data/data/languages/chinese.json b/dist/ba_data/data/languages/chinese.json index a956861..965cc2b 100644 --- a/dist/ba_data/data/languages/chinese.json +++ b/dist/ba_data/data/languages/chinese.json @@ -11,7 +11,7 @@ "linkAccountsEnterCodeText": "输入代码", "linkAccountsGenerateCodeText": "生成代码", "linkAccountsInfoText": "(在不同的平台上同步游戏进程)", - "linkAccountsInstructionsNewText": "要关联两个帐户,首先点“生成代码”\n,在第二设备点“输入代码”输入。\n两个帐户数据将被两者共享。\n\n您最多可以关联${COUNT}个帐户。\n(包括自己的账户)\n\n最好只关联自己的用户,\n避免对方在30天之后\n取消你的关联,造成损失。", + "linkAccountsInstructionsNewText": "要关联两个帐户,首先点“生成代码”\n,在第二设备点“输入代码”输入。\n两个帐户数据将被两者共享。\n\n您最多可以关联${COUNT}个帐户。\n(包括自己的账户)\n\n重要提示:最好只关联自己的账户;\n如果你与朋友的账户关联了,那么\n你们将不能同时游玩线上模式。", "linkAccountsInstructionsText": "若要关联两个账户,在其中一个账户内\n生成一个代码,用以在另一个账户内输入。\n游戏进程和物品将会被合并。\n您最多可以关联${COUNT}个账户\n\n重要:只能关联您自己的帐户!\n如果您跟您的朋友关联帐户\n您将无法在同一时间玩\n\n另外:此操作目前不能撤销,所以要小心!", "linkAccountsText": "关联账户", "linkedAccountsText": "已关联的账户:", @@ -61,7 +61,7 @@ "name": "拳王" }, "Dual Wielding": { - "descriptionFull": "连接两个控制手柄(硬件或应用)(耳机按钮可达到链接效果)", + "descriptionFull": "连接两个控制手柄(硬件或应用)", "descriptionFullComplete": "已经连接两个控制手柄(硬件或应用)", "name": "成双成对" }, @@ -366,9 +366,9 @@ "completeThisLevelToProceedText": "你需要先完成这一关", "completionBonusText": "完成奖励", "configControllersWindow": { - "configureControllersText": "手柄调试", - "configureKeyboard2Text": "设置键盘 P2", - "configureKeyboardText": "设置键盘", + "configureControllersText": "手柄配置", + "configureKeyboard2Text": "键盘设置 P2", + "configureKeyboardText": "键盘配置", "configureMobileText": "用移动设备作为控制器", "configureTouchText": "触摸屏配置", "ps3Text": "PS3手柄", @@ -377,9 +377,9 @@ "xbox360Text": "Xbox360手柄" }, "configGamepadSelectWindow": { - "androidNoteText": "注意:手柄支持取决于设备和安卓版本。", - "pressAnyButtonText": "按手柄上的任意按钮\n 您想要设置...", - "titleText": "手柄调试" + "androidNoteText": "注意:是否支持手柄取决于设备和安卓版本。", + "pressAnyButtonText": "按手柄上的任意按钮\n 您想要配置...", + "titleText": "手柄配置" }, "configGamepadWindow": { "advancedText": "高级", @@ -411,7 +411,7 @@ "runButton2Text": "跑 按键2", "runTrigger1Text": "跑 扳机1", "runTrigger2Text": "跑 扳机2", - "runTriggerDescriptionText": "(模拟扳机可实现变速运行)", + "runTriggerDescriptionText": "(模拟扳机可实现变速奔跑)", "secondHalfText": "用于设置显示为单一手柄的\n二合一手柄设备的\n第二部分。", "secondaryEnableText": "启用", "secondaryText": "从属手柄", @@ -825,6 +825,7 @@ "bombInfoText": "炸弹\n比拳头伤害高,但也能把自己送上西天。\n给你个建议:等引线快烧完的时候\n再把炸弹扔向敌人。", "canHelpText": "${APP_NAME}可以帮助。", "controllersInfoText": "你可以和好友在同一网络下玩${APP_NAME},或者\n如果你有足够多的手柄,那也可以在同一个设备上游戏。\n${APP_NAME}支持各种选择;你甚至可以通过免费的'${REMOTE_APP_NAME}'\n用手机作为游戏手柄。\n更多信息,请参见设置->手柄。", + "controllersInfoTextRemoteOnly": "你可以通过网络与你的朋友们一起游玩${APP_NAME}\n或者你可以使用${REMOTE_APP_NAME}\n它会将你的手机作为手柄在同一个设备上与你的朋友一起游玩", "controllersText": "手柄", "controlsSubtitleText": "你的友好的${APP_NAME}角色具有几个基本动作:", "controlsText": "控制键", @@ -835,12 +836,12 @@ "jumpInfoText": "跳跃\n跳跃可以跳过较窄的缝隙,\n或是把炸弹扔的更远,\n或是表达你难以掩盖的喜悦之情。", "orPunchingSomethingText": "或用拳猛击敌人,将它砸下悬崖,然后在它下落的途中用粘性炸弹炸掉它。", "pickUpInfoText": "拾起\n你可以拾起旗子,敌人,\n还有所有没固定在地上的东西,\n然后,再扔出去吧。", - "powerupBombDescriptionText": "连续扔出\n三枚炸弹。", + "powerupBombDescriptionText": "将炸弹最大投掷数量\n由一个提升为三个", "powerupBombNameText": "三连炸弹", "powerupCurseDescriptionText": "你可能想要避开这些。\n…或者你想试试看?", "powerupCurseNameText": "诅咒", - "powerupHealthDescriptionText": "让你完全恢复生命值。\n你永远都猜不到。", - "powerupHealthNameText": "中等生命值包", + "powerupHealthDescriptionText": "完全回血!\n想不到吧!", + "powerupHealthNameText": "医疗包", "powerupIceBombsDescriptionText": "威力比普通炸弹小,\n但能将你的敌人冻住,\n让它们变得特别脆弱。", "powerupIceBombsNameText": "冰冻弹", "powerupImpactBombsDescriptionText": "威力比普通炸弹稍弱,\n但碰到外物后就会爆炸。", @@ -854,8 +855,8 @@ "powerupStickyBombsDescriptionText": "黏在任何碰到的东西上,\n然后就等着看烟花吧。", "powerupStickyBombsNameText": "粘性炸弹", "powerupsSubtitleText": "当然,没有提升器的游戏很难通关:", - "powerupsText": "提升器", - "punchInfoText": "拳击\n跑得越快,拳击的伤害越高。\n所以请成为飞奔的拳击手吧!", + "powerupsText": "加成", + "punchInfoText": "拳击\n跑得越快,拳击的伤害\n越高。所以像疯子一样\n旋转跳跃吧!", "runInfoText": "冲刺\n按任意键冲刺,如果你用手柄操作将会容易许多。\n冲刺跑的虽快,但会造成转向困难。且冲且珍惜。", "someDaysText": "有些时候你只是想挥拳猛击某些东西,或把什么东西给炸飞。", "titleText": "${APP_NAME}帮助", @@ -1021,9 +1022,9 @@ "modeArcadeText": "街机模式", "modeClassicText": "经典模式", "modeDemoText": "演示模式", - "mostValuablePlayerText": "最有价值的玩家", - "mostViolatedPlayerText": "最遭受暴力的玩家", - "mostViolentPlayerText": "最暴力的玩家", + "mostValuablePlayerText": "最具价值玩家", + "mostViolatedPlayerText": "最遭暴力玩家", + "mostViolentPlayerText": "最暴力玩家", "moveText": "移动", "multiKillText": "${COUNT}连杀!!", "multiPlayerCountText": "${COUNT}名玩家", @@ -1150,7 +1151,7 @@ "purchasingText": "正在购买…", "quitGameText": "退出${APP_NAME}?", "quittingIn5SecondsText": "在5秒后退出...", - "randomPlayerNamesText": "Deva最萌, 企鹅王, 企鹅骑士团成员, 王♂の传人, 挨揍使我快乐, 正义之雷, 炸弹超人, 天下谁能敌手, 坑死队友不偿命, ChineseBomber, 一拳超人, 比尔, 二营长の意大利炮, 雷王, 野渡无人舟自横, 马克斯, 雪糕, 炸鸡翅, 手柄玩家18子, 寻找宝藏的海盗, 炸弹投手, 炸弹不是糖果, 我是对面的, Xxx_至高无上之炸弹王_xxX,万有引力,鸟语花香,狗年大吉,小狗狗,大狗子,二狗子,三狗子,四狗子,五狗子,灵虹膜", + "randomPlayerNamesText": "Deva最萌,企鹅王,企鹅骑士团成员,王♂の传人,挨揍使我快乐,ChineseBomber,一拳超人,二营长の意大利炮,野渡无人舟自横,马克斯,雪糕,炸鸡翅,手柄玩家18子,寻找宝藏的海盗,炸弹投手,炸弹不是糖果,我是对面的,Xxx_至高无上之炸弹王_xxX,万有引力,鸟语花香,狗年大吉,小狗狗,大狗子,二狗子,三狗子,四狗子,五狗子,高质量人类,吴签,菜虚困,劈我瓜是吧,是我dio哒,亚达哟", "randomText": "随机", "rankText": "排行", "ratingText": "排名", @@ -1240,7 +1241,7 @@ "disableCameraGyroscopeMotionText": "禁用相机陀螺仪运动", "disableCameraShakeText": "禁用相机抖动", "disableThisNotice": "(可在高级设置中关闭此通知)", - "enablePackageModsDescriptionText": "(启用额外的修改性能,但是禁用网络播放)", + "enablePackageModsDescriptionText": "(启用额外的模组功能,但是禁用多人模式)", "enablePackageModsText": "启用本地程序包修改", "enterPromoCodeText": "输入促销代码", "forTestingText": "注意:这些数值仅用于测试,并会在应用程序退出时丢失。", @@ -1295,7 +1296,7 @@ "Victory": "最终得分屏幕" }, "spaceKeyText": "空格", - "statsText": "统计", + "statsText": "详情", "storagePermissionAccessText": "需要存储权限", "store": { "alreadyOwnText": "您已拥有${NAME}!", @@ -1384,8 +1385,8 @@ "Easter Bunny": "复活兔", "Flopsy": "萌兔耷拉", "Frosty": "冰冰", - "Gretel": "歌者格蕾特", - "Grumbledorf": "男巫道傅", + "Gretel": "格蕾特", + "Grumbledorf": "格朗布多尔夫", "Jack Morgan": "杰克摩根", "Kronk": "克罗克", "Lee": "李", @@ -1402,7 +1403,7 @@ "Taobao Mascot": "淘公仔", "Todd McBurton": "托德马克波顿", "Zoe": "佐伊", - "Zola": "刺杀者佐拉" + "Zola": "佐拉" }, "coopLevelNames": { "${GAME} Training": "${GAME}训练", @@ -1411,7 +1412,7 @@ "Infinite Runaround": "无限塔防战", "Onslaught Training": "冲锋训练", "Pro ${GAME}": "专业版${GAME}", - "Pro Football": "专业足球战", + "Pro Football": "专业橄榄球赛", "Pro Onslaught": "专业冲锋战", "Pro Runaround": "专业塔防战", "Rookie ${GAME}": "新手版${GAME}", @@ -1440,12 +1441,12 @@ "Last remaining alive wins.": "最终幸存者获胜。", "Last team standing wins.": "最终杀敌团队获胜。", "Prevent enemies from reaching the exit.": "阻止敌人到达出口。", - "Reach the enemy flag to score.": "抵达敌人的旗帜来得分。", + "Reach the enemy flag to score.": "触碰敌人的旗帜来得分。", "Return the enemy flag to score.": "交回敌人的旗帜来得分。", "Run ${ARG1} laps.": "跑${ARG1}圈。", - "Run ${ARG1} laps. Your entire team has to finish.": "跑${ARG1}圈。你的整个团队必须来完成。", + "Run ${ARG1} laps. Your entire team has to finish.": "跑${ARG1}圈。你的整个团队都得完成。", "Run 1 lap.": "跑1圈。", - "Run 1 lap. Your entire team has to finish.": "跑1圈。你的整个团队必须来完成。", + "Run 1 lap. Your entire team has to finish.": "跑1圈。你的整个团队都得完成。", "Run real fast!": "快速奔跑!", "Score ${ARG1} goals.": "${ARG1}进球得分。", "Score ${ARG1} touchdowns.": "${ARG1}触地得分。", @@ -1484,7 +1485,7 @@ "Chosen One": "选定模式", "Conquest": "征服战", "Death Match": "死亡竞赛", - "Easter Egg Hunt": "猎蛋复活者", + "Easter Egg Hunt": "彩蛋猎人", "Elimination": "消除战", "Football": "运旗战", "Hockey": "冰球战", @@ -1533,6 +1534,7 @@ "Slovak": "斯洛伐克语", "Spanish": "西班牙语", "Swedish": "瑞典语", + "Thai": "泰语", "Turkish": "土耳其语", "Ukrainian": "乌克兰语", "Venetian": "威尼斯语", @@ -1550,7 +1552,7 @@ "Courtyard": "庭院地图", "Crag Castle": "岩城地图", "Doom Shroom": "末日蘑菇地图", - "Football Stadium": "足球场地图", + "Football Stadium": "橄榄球场", "Happy Thoughts": "快乐想法", "Hockey Stadium": "曲棍球场地图", "Lake Frigid": "寒湖地图", @@ -1581,6 +1583,7 @@ "Account linking successful!": "账号连接成功!", "Account unlinking successful!": "取消关联账户成功!", "Accounts are already linked.": "账号已经连接。", + "Ad view could not be verified.\nPlease be sure you are running an official and up-to-date version of the game.": "广告加载失败。\n请验证你的游戏版本为官方最新版。", "An error has occurred; (${ERROR})": "出现了一个错误; (${ERROR})", "An error has occurred; please contact support. (${ERROR})": "出现了一个错误,请联系客服获取支持.(${ERROR})", "An error has occurred; please contact support@froemling.net.": "发生了一个错误,请联系 support@froemling.net。", @@ -1653,7 +1656,7 @@ "4 Seconds": "4秒钟", "5 Minutes": "5分钟", "8 Seconds": "8秒钟", - "Allow Negative Scores": "允許負分", + "Allow Negative Scores": "允许负分", "Balance Total Lives": "平衡总生命", "Bomb Spawning": "生成炸弹", "Chosen One Gets Gloves": "选定目标获取手套", @@ -1761,11 +1764,11 @@ "phrase10Text": "奔跑也会发挥作用。", "phrase11Text": "按住任意按钮来奔跑。", "phrase12Text": "如要获得超赞的出拳,请尝试持续奔跑并旋转。", - "phrase13Text": "哎呦;关于${NAME}十分抱歉。", + "phrase13Text": "啊;非常不好意思,${NAME}。", "phrase14Text": "你可以捡起并投掷物体,如旗帜或${NAME}。", "phrase15Text": "最后,还有炸弹。", "phrase16Text": "投掷炸弹需要练习。", - "phrase17Text": "哎哟!这一记投掷并不漂亮。", + "phrase17Text": "哎哟!这一下投的不咋地啊。", "phrase18Text": "移动有助你投掷得更远。", "phrase19Text": "跳跃有助你投掷得更高。", "phrase20Text": "“鞭打”你的炸弹以抛出更远的距离。", diff --git a/dist/ba_data/data/languages/chinesetraditional.json b/dist/ba_data/data/languages/chinesetraditional.json index d08f079..b2a7a87 100644 --- a/dist/ba_data/data/languages/chinesetraditional.json +++ b/dist/ba_data/data/languages/chinesetraditional.json @@ -814,6 +814,7 @@ "bombInfoText": "—炸彈—\n比拳頭傷害高,但也能對自己造成傷害\n給你個建議:\n在引線快要燒完時\n把炸彈扔向敵人", "canHelpText": "${APP_NAME}可以給你幫助", "controllersInfoText": "你可以正在局域網環境下與其他玩家遊玩${APP_NAME} ,或者\n你有足夠多的遊戲手柄,那樣也可以在同一個設備下游戲\n${APP_NAME}支持各種選擇;你甚至可以通過免費的 '${REMOTE_APP_NAME}' \n用手機或平板電腦作為遊戲手柄\n更多信息,請參考\"設置—>控制器\"", + "controllersInfoTextRemoteOnly": "你可以和好友在同一網路下玩${APP_NAME},或者你們\n可以通過使用免費的應用'${REMOTE_APP_NAME}'\n來將手機作爲遊戲控制手柄在同一個設備上遊戲", "controllersText": "手柄", "controlsSubtitleText": "你的好友的${APP_NAME}角色具有幾個基本動作", "controlsText": "控制鍵", @@ -1511,6 +1512,7 @@ "Slovak": "斯洛伐克語", "Spanish": "西班牙語", "Swedish": "瑞典語", + "Thai": "泰語", "Turkish": "土耳其語", "Ukrainian": "烏克蘭語", "Venetian": "威尼斯語", @@ -1559,6 +1561,7 @@ "Account linking successful!": "賬號關聯成功", "Account unlinking successful!": "取消關聯成功", "Accounts are already linked.": "賬號已經連接", + "Ad view could not be verified.\nPlease be sure you are running an official and up-to-date version of the game.": "廣告加載失敗。\n請驗證遊戲版本為官方最新版", "An error has occurred; (${ERROR})": "出現了一個錯誤(${ERROR})", "An error has occurred; please contact support. (${ERROR})": "出現了一個錯誤,請聯繫客服解決(${ERROR})", "An error has occurred; please contact support@froemling.net.": "發生了一個錯誤,請聯繫support@froemling.net", diff --git a/dist/ba_data/data/languages/czech.json b/dist/ba_data/data/languages/czech.json index aa35848..39214ce 100644 --- a/dist/ba_data/data/languages/czech.json +++ b/dist/ba_data/data/languages/czech.json @@ -828,6 +828,7 @@ "bombInfoText": "- Bomba -\nSilnější než pěsti, ale\nmůže dojít k sebe-zraněním.\nNejlepší bude, když hodíte bombu\nna nepřítele dříve, než vyprší její čas.", "canHelpText": "${APP_NAME} může pomoci.", "controllersInfoText": "Můžete hrát ${APP_NAME} s přáteli přes síť nebo můžete, pokud máte\ndostatek ovladačů, hrát všichni na stejném zařízení. ${APP_NAME}\njich podporuje nepřeberné množství. Navíc můžete použít svoje telefony\njako ovladače pomocí aplikace '${REMOTE_APP_NAME}', která\nje zdarma. Podívejte se do Nastavení->Ovladače pro více informací.", + "controllersInfoTextRemoteOnly": "Hrajte ${APP_NAME} s přáteli přes internet \nnebo všichni na jednom zařízení za pomoci \nGamepadů nebo mobilní aplikace '${REMOTE_APP_NAME}'", "controllersText": "Ovladače", "controlsSubtitleText": "Vaše přátelská ${APP_NAME} postava má pár základních schopností:", "controlsText": "Ovládání", @@ -1534,6 +1535,7 @@ "Slovak": "Slovenština", "Spanish": "Španělština", "Swedish": "Švédština", + "Thai": "Thaiština", "Turkish": "Turečtina", "Ukrainian": "Ukrajinština", "Venetian": "Benátština", @@ -1582,6 +1584,7 @@ "Account linking successful!": "Spojení účtu úspěšné!", "Account unlinking successful!": "Účet úspěšně odpojen!", "Accounts are already linked.": "Účty jsou již spojeny.", + "Ad view could not be verified.\nPlease be sure you are running an official and up-to-date version of the game.": "Zhlédnutí reklamy nebylo ověřeno.\nProsím ujistěte se, že máte oficiální a updatovanou hru.", "An error has occurred; (${ERROR})": "Nastala chyba; (${ERROR})", "An error has occurred; please contact support. (${ERROR})": "Nastala chyba; kontaktujete prosím podporu. (${ERROR})", "An error has occurred; please contact support@froemling.net.": "Stala se chyba; prosím kontaktujte support@froemling.net.", @@ -1607,6 +1610,7 @@ "Max number of profiles reached.": "Dosaženo maximalního počtu profilů", "Maximum friend code rewards reached.": "Kamarádova maximální výhra na kódu.", "Message is too long.": "Zpráva je příliš dlouhá.", + "No servers are available. Please try again soon.": "Žádné servery nejsou nyní dostupné. Zkuste to prosím později.", "Profile \"${NAME}\" upgraded successfully.": "Profil \"${NAME}\" byl úspěšně přeměněn.", "Profile could not be upgraded.": "Profil nelze přeměnit.", "Purchase successful!": "Koupě se zdařila!", @@ -1616,6 +1620,7 @@ "Sorry, this code has already been used.": "Omlouváme se, ale tento kód již byl použit.", "Sorry, this code has expired.": "Omlouváme se, ale platnost tohoto kódu vypršela.", "Sorry, this code only works for new accounts.": "Omlouváme se, ale tento kód je platný pouze pro nové účty.", + "Still searching for nearby servers; please try again soon.": "Stále se vyhledávají lokální servery; Prosím zkuste to později.", "Temporarily unavailable; please try again later.": "Dočasně nedostupné; zkuste to později.", "The tournament ended before you finished.": "Turnaj skončil než jste ho dokončili.", "This account cannot be unlinked for ${NUM} days.": "Tento účet nelze odpojit po dobu ${NUM} dnů.", diff --git a/dist/ba_data/data/languages/english.json b/dist/ba_data/data/languages/english.json index ecfc330..71fdd00 100644 --- a/dist/ba_data/data/languages/english.json +++ b/dist/ba_data/data/languages/english.json @@ -817,6 +817,7 @@ "bombInfoTextScale": 0.6, "canHelpText": "${APP_NAME} can help.", "controllersInfoText": "You can play ${APP_NAME} with friends over a network, or you\ncan all play on the same device if you have enough controllers.\n${APP_NAME} supports a variety of them; you can even use phones\nas controllers via the free '${REMOTE_APP_NAME}' app.\nSee Settings->Controllers for more info.", + "controllersInfoTextRemoteOnly": "You can play ${APP_NAME} with friends over a network, or you\ncan all play on the same device by using phones as\ncontrollers via the free '${REMOTE_APP_NAME}' app.", "controllersText": "Controllers", "controlsSubtitleText": "Your friendly ${APP_NAME} character has a few basic actions:", "controlsText": "Controls", @@ -1523,6 +1524,8 @@ "Slovak": null, "Spanish": null, "Swedish": null, + "Tamil": null, + "Thai": null, "Turkish": null, "Ukrainian": null, "Venetian": null, @@ -1571,6 +1574,7 @@ "Account linking successful!": null, "Account unlinking successful!": null, "Accounts are already linked.": null, + "Ad view could not be verified.\nPlease be sure you are running an official and up-to-date version of the game.": null, "An error has occurred; (${ERROR})": null, "An error has occurred; please contact support. (${ERROR})": null, "An error has occurred; please contact support@froemling.net.": null, diff --git a/dist/ba_data/data/languages/french.json b/dist/ba_data/data/languages/french.json index c05b372..711e687 100644 --- a/dist/ba_data/data/languages/french.json +++ b/dist/ba_data/data/languages/french.json @@ -862,6 +862,7 @@ "controllersInfoTextFantasia": "Un joueur peut utiliser la télécommande comme manette, mais les\nmanettes sont vivement recommandées. Vous pouvez toujours utiliser\ndes systèmes iOS/Android comme manettes via l'application gratuite\n'BombSquad'. Allez voir 'Manettes' dans 'Paramètres' pour plus d'infos.", "controllersInfoTextMac": "Un ou deux joueurs peuvent utiliser le clavier, mais Bombsquad est meilleur avec\nmanettes. BombSquad supporte des manettes USB, PS3, XBOX 360, Wiimotes\net des systèmes iOS/Android pour controler les personnages. J'espere que vous en avez. Allez voir 'Manettes' dans 'Paramètres' pour plus d'info.", "controllersInfoTextOuya": "Vous pouvez utiliser des manettes OUYA, PS3, XBOX 360 et beaucoup \nd'autres manettes USB et Bluetooth avec BombSquad. Vous pouvez aussi \nutiliser des sytèmes iOS et Android comme manette gratuitement via \nl'application 'BombSquad Remote'. Allez voir 'Manettes' dans 'Paramètres' \npour plus d'info.", + "controllersInfoTextRemoteOnly": "Vous pouvez jouer à ${APP_NAME} avec vos amis sur internet, ou vous\npouvez tous jouer sur le même appareil en utilisant des téléphones\ncomme manettes avec l'application gratuite '${REMOTE_APP_NAME}'", "controllersText": "Manettes", "controlsSubtitleText": "Votre personnage ${APP_NAME} possède plusieurs actions basiques:", "controlsText": "Contrôles", @@ -1611,6 +1612,7 @@ "Slovak": "Slovaque", "Spanish": "Espagnol", "Swedish": "Suédois", + "Thai": "Thaïlandais", "Turkish": "Turc", "Ukrainian": "Ukrainien", "Venetian": "Vénitien", @@ -1662,6 +1664,7 @@ "Account linking successful!": "Liaison des comptes réussie!", "Account unlinking successful!": "Compte dissocié avec succès!", "Accounts are already linked.": "Ces comptes sont déjà liés.", + "Ad view could not be verified.\nPlease be sure you are running an official and up-to-date version of the game.": "La vue de l'annonce n'a pas pu être vérifiée.\nVeuillez vous assurer que vous utilisez une version officielle et à jour du jeu.", "An error has occurred; (${ERROR})": "Une erreur est survenue; (${ERROR})", "An error has occurred; please contact support. (${ERROR})": "Une erreur est survenue; veuillez contacter le support.(${ERROR})", "An error has occurred; please contact support@froemling.net.": "Une erreur est survenue; SVP contacter support@froemling.net.", @@ -1687,6 +1690,7 @@ "Max number of profiles reached.": "Nombre maximum de profils atteint.", "Maximum friend code rewards reached.": "Maximum récompenses de code ami atteint.", "Message is too long.": "Message trop long.", + "No servers are available. Please try again soon.": "Aucun serveur disponible. S'il vous plaît réessayez plus tard.", "Profile \"${NAME}\" upgraded successfully.": "Le profil \"${NAME}\" à été mis à jour.", "Profile could not be upgraded.": "Le profil ne peut pas être mis à jour.", "Purchase successful!": "Achat réussi!", @@ -1696,6 +1700,7 @@ "Sorry, this code has already been used.": "Désolé, ce code a déjà été utilisé.", "Sorry, this code has expired.": "Désolé, ce code a expiré.", "Sorry, this code only works for new accounts.": "Désolé, ce code fonctionne seulement pour les nouveaux comptes.", + "Still searching for nearby servers; please try again soon.": "Recherche de serveurs à proximité en cours; s'il vous plaît réessayez plus tard.", "Temporarily unavailable; please try again later.": "Temporairement indisponible; veuillez réessayer plus tard.", "The tournament ended before you finished.": "Le tournoi s'est terminé avant que vous finissiez.", "This account cannot be unlinked for ${NUM} days.": "Ce compte ne peux pas être dissocié pendant ${NUM} jours.", diff --git a/dist/ba_data/data/languages/german.json b/dist/ba_data/data/languages/german.json index 9797827..a80cb66 100644 --- a/dist/ba_data/data/languages/german.json +++ b/dist/ba_data/data/languages/german.json @@ -872,6 +872,7 @@ "controllersInfoTextFantasia": "Ein Spieler kann die Fernbedienung als Controller verwenden,\naber Gamepads sind empfolen. Du kannst auch mobile Geräte\nals Controller benutzen. Downlode die kostenlose\n'BombSquad Remote' App. Schau unter 'Controller' in den \n'Einstellungen' für mehr Info.", "controllersInfoTextMac": "Ein oder zwei Spieler können die Tastatur benutzen, allerdings spielt sich\nBombSquad am Besten mit Gamepads. Bombsquad kann USB Gamepads,\nPS3 Controller, XBox 360 Controller, Wiimotes und iOS-/Androidgeräte\nbenutzen, um die Charakter zu steuern. Hoffentlich hast du einige von Diesen.\nIn den Einstellungen unter 'Controller' findest du weitere Informationen.", "controllersInfoTextOuya": "Du kannst OUYA Controller, PS3 Controller, XBox 360 Controller und viele weitere\nUSB und Bluetooth Gamepads mit BombSquad benutzen. Desweiteren kannst du\nauch iOS- und Androidgeräte mit der kostenlosen 'BombSquad Remote' App nutzen.\nIn den Einstellungen unter 'Controller' findest du weitere Informationen.", + "controllersInfoTextRemoteOnly": "Du kannst ${APP_NAME} mit freunden über ein Netzwerk, oder ihr\nkönnt alle auf einem Gerät spielen in dem ihr das Handy als\nController nutzt mit der kostenlosen app '${REMOTE_APP_NAME}'", "controllersInfoTextScaleFantasia": 0.51, "controllersInfoTextScaleMac": 0.58, "controllersInfoTextScaleOuya": 0.63, @@ -1634,6 +1635,7 @@ "Slovak": "Slovakisch", "Spanish": "Spanisch", "Swedish": "Schwedisch", + "Thai": "thailändisch", "Turkish": "Türkisch", "Ukrainian": "Ukrainisch", "Venetian": "Venezianisch", @@ -1685,6 +1687,7 @@ "Account linking successful!": "Accounts erfolgreich verknüpft!", "Account unlinking successful!": "Aufheben der Kontoverknüpfung erfolgreich!", "Accounts are already linked.": "Accounts sind schon verknüpft.", + "Ad view could not be verified.\nPlease be sure you are running an official and up-to-date version of the game.": "Die Anzeigenansicht konnte nicht überprüft werden.\nBitte stellen Sie sicher, dass Sie eine offizielle und aktuelle Version des Spiels verwenden.", "An error has occurred; (${ERROR})": "Ein Fehler ist aufgetreten; (${ERROR})", "An error has occurred; please contact support. (${ERROR})": "Ein Fehler ist aufgetreten; bitte kontaktiere den Support. (${ERROR})", "An error has occurred; please contact support@froemling.net.": "Ein Fehler ist aufgetreten; bitte kontaktiere: support@froemling.net.", diff --git a/dist/ba_data/data/languages/gibberish.json b/dist/ba_data/data/languages/gibberish.json index 21377ac..853492d 100644 --- a/dist/ba_data/data/languages/gibberish.json +++ b/dist/ba_data/data/languages/gibberish.json @@ -1,7 +1,7 @@ { "accountRejectedText": "You ac woefije obj acwoew. Aj cowier wore cs?", "accountSettingsWindow": { - "accountNameRules": "Acoief coej. c woejf. cwoef ocoweofwjfj c wjefowfowef wocjowefff", + "accountNameRules": "Acoief coej. c woejf. cwoef ocoweofwjfj c wjefowfowef wocjowefffz", "accountProfileText": "(acczntl prfflzlf)", "accountsText": "Acctntzz", "achievementProgressText": "Achilfjasdflz: ${COUNT} ouzt of ${TOTAL}", @@ -876,6 +876,7 @@ "controllersInfoTextFantasia": "Onfe ojfr twjo ploayers cjan usje tjhe keyoiboard, bjut BombjjSquad ifs bfest width gamjepads.\nWiimotejs, adnd iOS/Android devfdsfs to condsdfrol chasdfcters. Hopezzfully you havesf\nsomef of thesfe handffy. See 'Controllefdrs' undzer 'Settinfewgs' fofr moree inefo.", "controllersInfoTextMac": "Onfe ojfr twjo ploayers cjan usje tjhe keyoiboard, bjut BombjjSquad ifs bfest width gamjepads.\nBombSqufad cajn ufe USB gamepd, PS3 codzntrollers, XBsox 360 codsfnolers,\nWiimotejs, adnd iOS/Android devfdsfs to condsdfrol chasdfcters. Hopezzfully you havesf\nsomef of thesfe handffy. See 'Controllefdrs' undzer 'Settinfewgs' fofr moree inefo.", "controllersInfoTextOuya": "Yzz czf usfd OUYA contdsfasdf, PS3 conconafdsf, XBox 360 coancofffnf,\nafg ladsf of ofafsdf USB and Bluetooth gamepdfhfj with BombSquadz.\nYouf canzz alssf use ijOS aknd Androioid dejoivices as cojjntrollers vjia thej fjree\n'BombSquadj Remoftef' appf. Seef 'Controllerss' underz 'Settinfgsf' fozr mosre inffo.", + "controllersInfoTextRemoteOnly": "Yeof ocowef ${APP_NAME} owejc oefw eoeo owjocejore,\npower cue afoot focjeo foweifjwoeh coweifowpa oghwoef\ncocwoejrw. cow;oiwjrweirw '${REMOTE_APP_NAME}' apz.", "controllersInfoTextScaleFantasia": 0.56, "controllersInfoTextScaleMac": 0.58, "controllersInfoTextScaleOuya": 0.63, @@ -1643,6 +1644,8 @@ "Slovak": "Slihdtbjoy", "Spanish": "Snsdnsh", "Swedish": "Swdiiszh", + "Tamil": "Tmfiewf", + "Thai": "Thzff", "Turkish": "Twfoijwef", "Ukrainian": "Ukckwef", "Venetian": "Vwvowefdf", @@ -1694,6 +1697,7 @@ "Account linking successful!": "Accjo link succeosf!", "Account unlinking successful!": "Accjow cowejowejr sucefwewr!", "Accounts are already linked.": "Accojif co woie owjilkn.", + "Ad view could not be verified.\nPlease be sure you are running an official and up-to-date version of the game.": "And foci wej fowif ijcowjer.\nPle eocj w jocose fowl jeowijeo few fj cijwoiejr. fofofifjow e ciweocywe.", "An error has occurred; (${ERROR})": "An cow fc woof wcef; (${ERROR})", "An error has occurred; please contact support. (${ERROR})": "An cowed c woefj wcoi woof weiojrwe rj goije ${ERROR})", "An error has occurred; please contact support@froemling.net.": "An f oco ao ocio wj ; pocj oco woei fsuupo co co ifoiwnet", diff --git a/dist/ba_data/data/languages/greek.json b/dist/ba_data/data/languages/greek.json index 3942da1..5e53f1d 100644 --- a/dist/ba_data/data/languages/greek.json +++ b/dist/ba_data/data/languages/greek.json @@ -679,11 +679,18 @@ "bluetoothJoinText": "Ενταχθείτε μέσω Bluetooth", "bluetoothText": "Bluetooth", "checkingText": "έλεγχος...", + "copyCodeConfirmText": "Ο κωδικός αντιγράφηκε στο πρόχειρο.", + "copyCodeText": "Αντιγράψτε τον κωδικό", "dedicatedServerInfoText": "Για καλύτερα αποτελέσματα, οργανώστε έναν σταθερό διακομιστή. Βλέπε bombsquadgame.com/server.", "disconnectClientsText": "Συνεχίζοντας θα αποσυνδεθούν ${COUNT} παίκης/ες\nαπο τη συγκέντρωσή σας. Είστε σίγουροι?", "earnTicketsForRecommendingAmountText": "Οι φίλοι σας θα λάβουν ${COUNT} εισητήρια αν δοκιμάσουν το παιχνίδι\n(και εσείς θα λάβετε ${YOU_COUNT} για τον καθένα)", "earnTicketsForRecommendingText": "Μοιραστείτε το παιχνίδι\nγια δωρεάν εισητήρια...", "emailItText": "Στείλτε το σε Email", + "favoritesSaveText": "Αποθήκευση ως αγαπημένο", + "favoritesText": "Αγαπημένα", + "freeCloudServerAvailableMinutesText": "Ο επόμενος δωρεάν cloud διακομιστής διαθέσιμος σε ${MINUTES} λεπτά.", + "freeCloudServerAvailableNowText": "Ο δωρεάν cloud διακομιστής είναι διαθέσιμος!", + "freeCloudServerNotAvailableText": "Δεν υπάρχουν δωρεάν cloud διακομιστές διαθέσιμοι.", "friendHasSentPromoCodeText": "${COUNT} εισητήρια ${APP_NAME} από ${NAME}", "friendPromoCodeAwardText": "Θα λάμβάνετε από ${COUNT} εισητήρια για κάθε χρήση.", "friendPromoCodeExpireText": "Αυτός ο κωδικός λήγει σε ${EXPIRE_HOURS} ώρες και λειτουργεί μόνο για νέα μέλη.", @@ -698,19 +705,21 @@ "googlePlaySeeInvitesText": "Προβολή Προσκλήσεων", "googlePlayText": "Google Play", "googlePlayVersionOnlyText": "(Android / Google Play έκδοση)", - "hostPublicPartyDescriptionText": "Φιλοξενίστε Δημόσια Συγκέντρωση:", + "hostPublicPartyDescriptionText": "Φιλοξενίστε Δημόσια Συγκέντρωση", + "hostingUnavailableText": "Η φιλοξενία δεν είναι διαθέσιμη.", "inDevelopmentWarningText": "Σημείωση:\n\nΤο παιχνίδι μέσω ιστού είναι καινούριο και εξελισσόμενο\nχαρακτηριστηκό. Για την ώρα, είναι πολύ προτεινόμενο όλοι\nοι παίκτες να είναι συνδεδεμένοι στο ίδιο δίκτυο Wi-Fi.", "internetText": "Διαδίκτυο", "inviteAFriendText": "Οι φίλοι σας δεν έχουν το παιχνίδι? Προσκαλέστε τους\nνα το δοκιμάσουν και θα λάβουν ${COUNT} δωρεάν εισιτήρια.", "inviteFriendsText": "Προσκαλέστε Φίλους", - "joinPublicPartyDescriptionText": "Ένταξη σε Δημόσια Συγκέντρωση:", - "localNetworkDescriptionText": "Ένταξη σε συγκέντρωση στο δίκτυο σας:", + "joinPublicPartyDescriptionText": "Ένταξη σε Δημόσια Συγκέντρωση", + "localNetworkDescriptionText": "Ένταξη σε συγκέντρωση στο δίκτυο σας (LAN, Bluetooth, κλπ.)", "localNetworkText": "Τοπικό Δίκτυο", "makePartyPrivateText": "Κάνε Την Συγκέντρωσή Μου Ιδιωτική", "makePartyPublicText": "Κάνε Την Συγκέντρωσή Μου Δημόσια", "manualAddressText": "Διεύθυνση", "manualConnectText": "Σύνδεση", "manualDescriptionText": "Ένταξη σε συγκέντρωση από διεύθυνση:", + "manualJoinSectionText": "Ένταξη Με Διεύθυνση", "manualJoinableFromInternetText": "Μπορείτε να φιλοξενήσετε από το διαδίκτυο;:", "manualJoinableNoWithAsteriskText": "ΟΧΙ*", "manualJoinableYesText": "ΝΑΙ", @@ -718,14 +727,17 @@ "manualText": "Χειροκίνητα", "manualYourAddressFromInternetText": "Η διεύθυνση σας με βάση το διαδίκτυο:", "manualYourLocalAddressText": "Η τοπική σας διεύθυνση:", + "nearbyText": "Κοντινά", "noConnectionText": "<εκτός σύνδεσης>", "otherVersionsText": "(άλλες εκδόσεις)", + "partyCodeText": "Κωδικός Party", "partyInviteAcceptText": "Αποδοχή", "partyInviteDeclineText": "Απόρριψη", "partyInviteGooglePlayExtraText": "(δείτε την καρτέλα 'Google Play' στο παράθυρο 'Συγκέντρωση')", "partyInviteIgnoreText": "Αγνόηση", "partyInviteText": "Ο χρήστης ${NAME} σας προσκάλεσε\nνα συμμετάσχετε στη συγκέντρωσή του!", "partyNameText": "Όνομα Συγκέντρωσης", + "partyServerRunningText": "Ο party διακομιστής σου τρέχει.", "partySizeText": "μέγεθος συγκέντρωσης", "partyStatusCheckingText": "έλεγχος κατάστασης...", "partyStatusJoinableText": "η συγκέντρωσή σας μπορεί πλέον να φιλοξενήσει από το διαδίκτυο", @@ -734,10 +746,20 @@ "partyStatusNotPublicText": "η συγκέντρωσή σας δεν είναι δημόσια", "pingText": "ping", "portText": "Θύρα", + "privatePartyCloudDescriptionText": "Τα ιδιωτικά party τρεχουν σε αφιερωμένους cloud διακομιστές, δεν χρειάζεται διαμόρφωση του ρουτερ.", + "privatePartyHostText": "Φιλοξενίστε ένα Ιδιωτικό Party", + "privatePartyJoinText": "Ένταξη σε Ιδιωτικό Party", + "privateText": "Ιδιωτικό", + "publicHostRouterConfigText": "Αυτό μπορεί να χρειαστεί διαμόρφωση του port-forwarding στο ρούτερ σου. Για μια ευκολότερη επιλογή, φιλοξενίστε ενα ιδιωτικό party.", + "publicText": "Δημόσιο", "requestingAPromoCodeText": "Αίτημα κωδικού...", "sendDirectInvitesText": "Αποστολή Άμεσων Προσκλήσεων", "shareThisCodeWithFriendsText": "Μοιραστείτε αυτόν τον κωδικό με φίλους σας:", "showMyAddressText": "Εμφάνισε τη Διεύθυνσή μου", + "startHostingPaidText": "Φιλοξενίστε Τώρα Για ${COST}", + "startHostingText": "Φιλοξενίστε", + "startStopHostingMinutesText": "Μπορείτε να ξεκινήσετε και να σταματήσετε να φιλοξενήσετε δωρεαν για τα επομενα ${MINUTES} λεπτά.", + "stopHostingText": "Σταματήσετε να φιλοξενήσετε", "titleText": "Συγκέντρωση", "wifiDirectDescriptionBottomText": "Εάν όλες οι συσκευές έχουν πίνακα 'Wi-Fi Direct', είναι να δυνατό μπορούν να τον χρησιμοποιήσουν \nγια να συνδεθούν μεταξύ τους. Όταν όλες οι συσκευές είναι συνδεδεμένες, μπορείτε να οργανώσετε\nσυγκεντρώσεις χρησιμοποιώντας τη καρτέλα 'Τοπικό Δίκτυο',όπως ακριβώς θα κάνατε και με το δίκτυο Wi-Fi.\n\nΓια καλύτερα αποτελέσματα, ο οικοδεσπότης Wi-Fi Direct θα πρέπει να είναι και ο οικοδεσπότης της ${APP_NAME} συγκέντρωσης.", "wifiDirectDescriptionTopText": "Το Wi-Fi Direct μπορεί να χρησιμοποιηθεί για την άμεση σύνδεση Android συσκευών χωρίς\nτη χρήση Wi-Fi δικτύου. Αυτή η μέθοδος λειτουργεί καλύτερα με Android 4.2 ή νεότερο.\n\nΓια να το χρησιμοποιήσετε, ανοίξτε τις ρυθμίσεις Wi-Fi και ψάξτε γιά το μενού 'Wi-Fi Direct'.", @@ -795,6 +817,7 @@ "bombInfoTextScale": 0.5, "canHelpText": "Το ${APP_NAME} μπορεί να βοηθήσει.", "controllersInfoText": "Μπορείτε να παίξετε ${APP_NAME} με φίλους μέσω ενός δικτύου ή μπορείτε\nόλοι να παίξετε στην ίδια συσκευή εάν έχετε αρκετά χειριστήρια. Το\n${APP_NAME} υποστηρίζει ποικιλία από αυτά. Μπορείτε ακόμα να\nχρησιμοποιήσετε κινητά τηλέφωνα ως χειριστήρια μέσω της δωρεάν εφαρμογής\n'${REMOTE_APP_NAME}'. Βλέπε Ρυθμίσεις->Χειριστήρια για περισσότερες πληροφορίες.", + "controllersInfoTextRemoteOnly": "Μπορείτε να παίξετε ${APP_NAME} με φιλους στο διαδίκτυο, ή μπορείτε\nνα παίξετε όλοι στην ίδια συσκευή χρησιμοποιώντας κινητά ως \nτηλεχειριστήρια μέσω της δωρεάν '${REMOTE_APP_NAME}'εφαρμογής.", "controllersText": "Χειριστήρια", "controlsSubtitleText": "Ο φιλικός σας ${APP_NAME} χαρακτήρας έχει μερικές βασικές κινήσεις:", "controlsText": "Χειρισμοί", @@ -1033,6 +1056,7 @@ "offText": "Κλειστό", "okText": "Εντάξει", "onText": "Ανοιχτό", + "oneMomentText": "Μια στιγμή...", "onslaughtRespawnText": "Ο παίκτης ${PLAYER} θα ξαναδημιουργηθεί στο κύμα ${WAVE}", "orText": "${A} ή ${B}", "otherText": "Άλλο...", @@ -1079,6 +1103,7 @@ "playerText": "Παίκτης", "playlistNoValidGamesErrorText": "Αυτή η λίστα αποτελείται από μη έγκυρα ξεκλειδωμένα παιχνίδια.", "playlistNotFoundText": "η λίστα δεν βρέθηκε", + "playlistText": "Λίστα αναπαραγωγής.", "playlistsText": "Λίστες Παιχνιδιών", "pleaseRateText": "Εάν απολαμβάνετε το ${APP_NAME}, παρακαλώ σκεφτείτε να αφιερώσετε μιά στιγμή\nγια να το βαθμολογήσετε ή να γράψετε μιά κριτική. Αυτό θα προσφέρει χρήσιμη\nανατροφοδότηση και θα βοηθήσει για την υποστήριξη της μέλλουσας ανάπτυξης.\n\nευχαριστώ!\n-eric", "pleaseWaitText": "Παρακαλώ περιμένετε...", @@ -1495,6 +1520,7 @@ "Slovak": "Σλοβακικά", "Spanish": "Ισπανικά", "Swedish": "Σουηδικά", + "Thai": "ταϊλανδέζικο", "Turkish": "Τούρκικα", "Ukrainian": "Ουκρανικά", "Venetian": "Ενετικά", @@ -1543,6 +1569,7 @@ "Account linking successful!": "Δέσμευση Λογαριασμών Επιτυχής!", "Account unlinking successful!": "Αποδέσμευση Λογαριασμών Επιτυχής!", "Accounts are already linked.": "Οι λογαριασμοί είναι ήδη δεσμευμένοι.", + "Ad view could not be verified.\nPlease be sure you are running an official and up-to-date version of the game.": "Το Ad view δεν μπορούσε να επαληθευτεί.\nΠαρακαλώ σιγουρευτείτε ότι τρέχετε μια επίσημη και σύγχρονη έκδοση του παιχνιδιού.", "An error has occurred; (${ERROR})": "Προέκυψε Σφάλμα. (${ERROR})", "An error has occurred; please contact support. (${ERROR})": "Προέκυψε Σφάλμα. Παρακαλώ επικοινωνήστε με την υποστήριξη. (${ERROR})", "An error has occurred; please contact support@froemling.net.": "Προέκυψε Σφάλμα. Παρακαλώ επικοινωνήστε με το support@froemling.net.", @@ -1568,6 +1595,7 @@ "Max number of profiles reached.": "Μέγιστος αριθμός προφίλ επετεύχθη.", "Maximum friend code rewards reached.": "Μέγιστος αριθμός ανταμοιβών κωδικού φίλων επετεύχθη.", "Message is too long.": "Το μήνυμα είναι πολύ μεγάλο.", + "No servers are available. Please try again soon.": "Καθόλου διακομιστές δεν ειναι διαθέσιμοι. Παρακαλώ δοκιμάστε ξανά αργότερα.", "Profile \"${NAME}\" upgraded successfully.": "Το Προφίλ \"${NAME}\" αναβαθμίστηκε επιτυχώς.", "Profile could not be upgraded.": "Το προφίλ δεν μπόρεσε να αναβαθμιστεί.", "Purchase successful!": "Επιτυχής αγορά!", @@ -1577,10 +1605,12 @@ "Sorry, this code has already been used.": "Συγνώμη, αυτός ο κωδικός έχει ήδη χρησιμοποιηθεί.", "Sorry, this code has expired.": "Συγνώμη, αυτός ο κωδικός έχει λήξει.", "Sorry, this code only works for new accounts.": "Συγνώμη, αυτός ο κωδικός λειτουργεί μονάχα για νέους λογαριασμούς.", + "Still searching for nearby servers; please try again soon.": "Ψάχνοντας για κοντινούς διακομιστές, παρακαλώ δοκιμάστε αργότερα.", "Temporarily unavailable; please try again later.": "Προς το παρόν μη διαθέσιμο. Παρακαλώ ξαναπροσπαθήστε αργότερα.", "The tournament ended before you finished.": "Το τουρνουά έληξε πριν τερματίσετε.", "This account cannot be unlinked for ${NUM} days.": "Αυτός ο λογαριασμός δεν μπορεί να αποδεσμευτεί για ${NUM} μέρες.", "This code cannot be used on the account that created it.": "Αυτός ο κωδικός δεν μπορεί να χρησιμοποιηθεί από τον λογαριασμό που τον δημιούργησε.", + "This is currently unavailable; please try again later.": "Αυτό δεν είναι διαθέσιμο, παρακαλώ δοκιμάστε ξανά αργότερα.", "This requires version ${VERSION} or newer.": "Αυτό απαιτεί έκδοση ${VERSION} ή νεότερη.", "Tournaments disabled due to rooted device.": "Τα τουρνουά απενεργοποιήθηκαν λόγω rooted συσκευής.", "Tournaments require ${VERSION} or newer": "Τα τουρνουά απαιτούν έκδοση ${VERSION} ή νεότερη", diff --git a/dist/ba_data/data/languages/hindi.json b/dist/ba_data/data/languages/hindi.json index d102105..807d3a8 100644 --- a/dist/ba_data/data/languages/hindi.json +++ b/dist/ba_data/data/languages/hindi.json @@ -822,6 +822,7 @@ "bombInfoText": "- बम - \nमुक्कों से ज्यादा शक्तिशाली परंतू \nखुद को भी नुक्सान पहुंचा सकते हैं | \nसबसे अच्छे परिणामों के लिए दुश्मन \nकि तरफ फूटने से पहले फेंके |", "canHelpText": "${APP_NAME} मदद कर सकता है |", "controllersInfoText": "आप नेटवर्क पे दोस्तों के साथ ${APP_NAME} खेल सकते हैं, या आप एक ही यंत्र पे भी खेल सकते हैं \nअगर आपके पास पर्याप्त नियंत्रक हैं | \n${APP_NAME} विविध नियंत्रकों को चला सकता है; \nआप फ़ोन का भी नियंत्रक के रूप में प्रयोग कर सकते है मुफ्त कि \n'${REMOTE_APP_NAME}' एप्लीकेशन द्वारा | अधिक जानकारी के लिए सेटिंग->नियंत्रक देखें |", + "controllersInfoTextRemoteOnly": "आप एक नेटवर्क पर दोस्तों के साथ ${APP_NAME} खेल सकते हैं, या आप \nसभी एक ही डिवाइस पर मुफ्त '${REMOTE_APP_NAME}' ऐप के \nमाध्यम से कंट्रोलर के रूप में फोन का उपयोग कर सकते हैं।", "controllersText": "नियंत्रक", "controlsSubtitleText": "आपका ${APP_NAME} पात्र कुछ बुनियादी कार्य कर सकता है", "controlsText": "नियंत्रण", @@ -1523,6 +1524,7 @@ "Slovak": "स्लोवाक", "Spanish": "स्पेनिश", "Swedish": "स्वीडिश", + "Thai": "थाई", "Turkish": "तुर्की", "Ukrainian": "यूक्रेनी", "Venetian": "वेनेशियन", @@ -1571,6 +1573,7 @@ "Account linking successful!": "खाता का जुड़ाव सफल!", "Account unlinking successful!": "खाता अलगाव सफल!", "Accounts are already linked.": "खाते पहले ही जुड़े हुए हैं।", + "Ad view could not be verified.\nPlease be sure you are running an official and up-to-date version of the game.": "विज्ञापन दृश्य सत्यापित नहीं किया जा सका.\nकृपया सुनिश्चित करें कि आप गेम का आधिकारिक और अप-टू-डेट संस्करण चला रहे हैं।", "An error has occurred; (${ERROR})": "एक गलती हुई है; (${ERROR})", "An error has occurred; please contact support. (${ERROR})": "एक गलती हुई है; कृपया समर्थन से संपर्क करें। (${ERROR})", "An error has occurred; please contact support@froemling.net.": "एक गलती हुई है; कृपया support@froemling.net से संपर्क करें।", @@ -1596,6 +1599,7 @@ "Max number of profiles reached.": "प्रोफाइल की अधिकतम संख्या तक पहुंच गया।", "Maximum friend code rewards reached.": "अधिकतम मित्र कोड पुरस्कार पहुंचे।", "Message is too long.": "संदेश बहुत लंबा है।", + "No servers are available. Please try again soon.": "कोई सर्वर उपलब्ध नहीं हैं। कृपया शीघ्र ही पुन: प्रयास करें।", "Profile \"${NAME}\" upgraded successfully.": "प्रोफाइल \"${NAME}\" सफलतापूर्वक अपग्रेड किया गया।", "Profile could not be upgraded.": "प्रोफ़ाइल को अपग्रेड नहीं किया जा सका।", "Purchase successful!": "खरीद सफल!", @@ -1605,6 +1609,7 @@ "Sorry, this code has already been used.": "क्षमा करें, यह कोड पहले ही इस्तेमाल हो चुका है।", "Sorry, this code has expired.": "क्षमा करें, यह कोड समाप्त हो गया है।", "Sorry, this code only works for new accounts.": "क्षमा करें, यह कोड केवल नए खातों के लिए काम करता है।", + "Still searching for nearby servers; please try again soon.": "अभी भी आस-पास के सर्वर खोज रहे हैं; कृपया जल्द ही पुन: प्रयास करें।", "Temporarily unavailable; please try again later.": "अस्थाई रूप से अनुपलब्ध; बाद में पुन: प्रयास करें।", "The tournament ended before you finished.": "टूर्नामेंट समाप्त होने से पहले समाप्त हो गया।", "This account cannot be unlinked for ${NUM} days.": "यह खाता ${NUM} दिनों के लिए अनलिंक नहीं किया जा सकता है।", diff --git a/dist/ba_data/data/languages/hungarian.json b/dist/ba_data/data/languages/hungarian.json index 7cf6ab0..039cc57 100644 --- a/dist/ba_data/data/languages/hungarian.json +++ b/dist/ba_data/data/languages/hungarian.json @@ -827,6 +827,7 @@ "bombInfoText": "- Bomba -\nErősebb, mint az ütés, de\neredményezhet komoly ön-sértést.\nA legjobb eredményért, dobj közel\naz elenség elé, mielőtt a kanóc kiég.", "canHelpText": "A ${APP_NAME} tud segíteni.", "controllersInfoText": "Játszhatsz ${APP_NAME}-ot a barátaiddal egy hálózaton keresztül, vagy\nmind tudtok játszani ugyanazon az eszközön, ha van elég kontrolleretek.\nA ${APP_NAME} támogat különböző fajtákat is; használhatod akár telefonod is\nmint kontroller az ingyenes '${REMOTE_APP_NAME}' alkalmazás által.\nLásd \"Beállítások->Vezérlők\" menüpontot további infókért.", + "controllersInfoTextRemoteOnly": "A (z) ${APP_NAME} játékot játszhatja barátaival a hálózaton keresztül, vagy veled\nmindannyian játszhatnak ugyanazon az eszközön a (z) telefonok használatával\nvezérlőket az ingyenes „${REMOTE_APP_NAME}” alkalmazáson keresztül.", "controllersText": "Vezérlők", "controlsSubtitleText": "A barátságos ${APP_NAME} karakterednek van pár alap képessége:", "controlsText": "Irányítás", @@ -1532,6 +1533,7 @@ "Slovak": "Szlovák", "Spanish": "Spanyol", "Swedish": "Svéd", + "Thai": "Thai, Thai ember", "Turkish": "Török", "Ukrainian": "Ukrán", "Venetian": "Velencei", @@ -1580,6 +1582,7 @@ "Account linking successful!": "A fiók összekötése sikeres!", "Account unlinking successful!": "A fiók leválasztása sikeres", "Accounts are already linked.": "A fiókok már össze vannak kötve.", + "Ad view could not be verified.\nPlease be sure you are running an official and up-to-date version of the game.": "A hirdetés megtekintését nem sikerült ellenőrizni.\nKérjük, győződjön meg róla, hogy a játék hivatalos és naprakész verzióját futtatja.", "An error has occurred; (${ERROR})": "Egy valamiféle hiba történt; (${ERROR})", "An error has occurred; please contact support. (${ERROR})": "Hiba történt; kérlek vedd fel a kapcsolatot az ügyfélszolgálattal. (${ERROR})", "An error has occurred; please contact support@froemling.net.": "Valamiféle hiba történt; kérlek lépj kapcsolatba velünk: support@froemling.net.", diff --git a/dist/ba_data/data/languages/indonesian.json b/dist/ba_data/data/languages/indonesian.json index 6414b41..5198c1c 100644 --- a/dist/ba_data/data/languages/indonesian.json +++ b/dist/ba_data/data/languages/indonesian.json @@ -741,7 +741,7 @@ "partySizeText": "ukuran", "partyStatusCheckingText": "memeriksa status...", "partyStatusJoinableText": "sekarang orang lain dapat gabung ke acaramu dari internet", - "partyStatusNoConnectionText": "Gak dapat nyambung ke server", + "partyStatusNoConnectionText": "Tidak dapat nyambung ke server", "partyStatusNotJoinableText": "orang lain gak dapat gabung ke acaramu lewat internet", "partyStatusNotPublicText": "acaramu bukan acara publik", "pingText": "Ping", @@ -816,6 +816,7 @@ "bombInfoText": "- Bomb -\nLebih kuat dari Tinju, tapi\ndapat menjadi bom bunuh diri.\ncoba untuk melempar sebelum\nsumbu akan habis.", "canHelpText": "${APP_NAME} Solusinya!", "controllersInfoText": "Kamu dapat bermain ${APP_NAME} dengan temanmu melalui sebuah\nJaringan, atau kamu dapat bermain dalam perangkat yang sama\njika kamu memiliki kontrol yang cukup. ${APP_NAME} menyediakan\npengontrol digital melalui aplikasi '${REMOTE_APP_NAME}'.\nlihat di Pengaturan -> Kontrol untuk info lebih lanjut.", + "controllersInfoTextRemoteOnly": "Anda bisa bermain ${APP_NAME} bersama dengan teman melalui jaringan, \natau kalian semua bisa bermain di perangkat yang sama dengan menggunakan ponsel sebagai pengontrol melalui aplikasi \n'${REMOTE_APP_NAME}' gratis.", "controllersText": "Kontrol", "controlsSubtitleText": "karakter ${APP_NAME} Memiliki beberapa gerakan dasar:", "controlsText": "Kontrol", @@ -1519,6 +1520,7 @@ "Slovak": "Slovakia", "Spanish": "Spanyol", "Swedish": "Swedia", + "Thai": "Thai", "Turkish": "Turki", "Ukrainian": "Ukraina", "Venetian": "Venesia", @@ -1567,6 +1569,7 @@ "Account linking successful!": "Berhasil menghubungkan akun!", "Account unlinking successful!": "Pemutusan akun berhasil!", "Accounts are already linked.": "Akun sudah dihubungkan.", + "Ad view could not be verified.\nPlease be sure you are running an official and up-to-date version of the game.": "Menonton iklan tidak dapat diverifikasi.\nPastikan Anda menjalankan versi game yang resmi dan terbaru.", "An error has occurred; (${ERROR})": "Sebuah kesalahan telah terjadi; (${ERROR})", "An error has occurred; please contact support. (${ERROR})": "Sebuah kesalahan telah terjadi; tolong hubungi dukungan. (${ERROR})", "An error has occurred; please contact support@froemling.net.": "Sebuah error telah terjadi; tolong hubungi support@froemling.net.", @@ -1592,6 +1595,7 @@ "Max number of profiles reached.": "Batas maksimum profil tercapai.", "Maximum friend code rewards reached.": "Batas maksimum hadiah kode teman tercapai.", "Message is too long.": "Pesan terlalu panjang.", + "No servers are available. Please try again soon.": "Tidak ada server yang Tersedia. silakan coba lagi nanti", "Profile \"${NAME}\" upgraded successfully.": "Profil \"${NAME}\" berhasil ditingkatkan.", "Profile could not be upgraded.": "Profile tidak dapat di tingkatkan.", "Purchase successful!": "Pembelian sukses!", @@ -1601,6 +1605,7 @@ "Sorry, this code has already been used.": "Maaf,kode ini sudah digunakan", "Sorry, this code has expired.": "Maaf, kode ini sudah kadaluarsa.", "Sorry, this code only works for new accounts.": "Maaf, kode ini hanya berlaku untuk akun baru.", + "Still searching for nearby servers; please try again soon.": "Masih mencari server terdekat; silahkan coba lagi nanti", "Temporarily unavailable; please try again later.": "Sedang tidak ada; mohon coba lagi nanti.", "The tournament ended before you finished.": "Turnamen berakhir sebelum Kamu selesai.", "This account cannot be unlinked for ${NUM} days.": "Akun ini tidak dapat diputuskan untuk ${NUM} hari.", diff --git a/dist/ba_data/data/languages/italian.json b/dist/ba_data/data/languages/italian.json index 17e954b..5247edb 100644 --- a/dist/ba_data/data/languages/italian.json +++ b/dist/ba_data/data/languages/italian.json @@ -759,7 +759,7 @@ "manualText": "Manuale", "manualYourAddressFromInternetText": "Il tuo indirizzo da internet:", "manualYourLocalAddressText": "Indirizzo locale:", - "nearbyText": "Proche", + "nearbyText": "Locale", "noConnectionText": "", "otherVersionsText": "(altre versioni)", "partyCodeText": "Code du parti", @@ -860,6 +860,7 @@ "controllersInfoTextFantasia": "Puoi usare il telecomando per giocare, ma suggerisco\nvivamente di usare i gamepad. Puoi anche usare i tuoi cellulari e\ntablet come controller usando l'app gratuita \"BombSquad Remote\".\nVai su \"Impostazioni\" e poi \"Controller\" per più informazioni.", "controllersInfoTextMac": "Uno o due giocatori possono usare la tastiera, ma BombSquad dà il meglio usando i Gamepad.\nPuoi controllare i personaggi usando Gamepad USB, controller PS3 o Xbox 360, Wiimote e \ndispositivi iOS/Android. Si spera che tu abbia alcuni di questi sotto mano. \nPer ulteriori informazioni vai su Impostazioni > Controller", "controllersInfoTextOuya": "Con BombSquad puoi utilizzare controller OUYA, PS3 e Xbox 360, e tanti\naltri Gamepad USB e Bluetooth. Puoi anche utilizzare dispositivi iOS e\nAndroid come controller tramite l'app gratuita 'BombSquad Remote'.\nPer ulteriori informazioni vai su Impostazioni > Controller.", + "controllersInfoTextRemoteOnly": "Tu puoi giocare a ${APP_NAME} con i tuoi amici su una connessione,\no puoi\nanche giocare sullo stesso dispositivo usando dei telefoni come controllers gratuitamente con l'app '${REMOTE_APP_NAME}'", "controllersText": "Controller", "controlsSubtitleText": "Il tuo amichevole personaggio di ${APP_NAME} ha poche azioni di base:", "controlsText": "Comandi", @@ -1598,6 +1599,7 @@ "Slovak": "Slovacco", "Spanish": "Spagnolo", "Swedish": "Svedese", + "Thai": "Tailandese", "Turkish": "Turco", "Ukrainian": "Ucraino", "Venetian": "Veneto", @@ -1649,6 +1651,7 @@ "Account linking successful!": "Account collegato correttamente!", "Account unlinking successful!": "Account scollegato con successo!", "Accounts are already linked.": "Questi account sono già collegati.", + "Ad view could not be verified.\nPlease be sure you are running an official and up-to-date version of the game.": "La visione della pubblicità non può essere verificata.\nper favore controlla di star usando una versione aggiornata del gioco.", "An error has occurred; (${ERROR})": "C'è stato un errore; (${ERROR})", "An error has occurred; please contact support. (${ERROR})": "C'è stato un errore; contatta il supporto. (${ERROR})", "An error has occurred; please contact support@froemling.net.": "Si è verificato un errore; per favore contattare support@froemling.net.", diff --git a/dist/ba_data/data/languages/korean.json b/dist/ba_data/data/languages/korean.json index f429d3f..b441e10 100644 --- a/dist/ba_data/data/languages/korean.json +++ b/dist/ba_data/data/languages/korean.json @@ -817,6 +817,7 @@ "bombInfoText": "- 폭탄 -\n펀치보다 강력하지만 자신도\n심각한 부상을 입을 수 있습니다.\n도화선이 다 타기 전에 적들에게\n던지는 것이 가장 좋습니다.", "canHelpText": "${APP_NAME}가 여러분을 도울 수 있습니다.", "controllersInfoText": "네트워크를 통해 친구들과 함께 ${APP_NAME}를 즐기거나\n컨트롤러가 충분할 경우 동일한 기기에서 함께 플레이할 수 있습니다.\n${APP_NAME}는 다양한 기기를 지원합니다. \n심지어 무료로 '${REMOTE_APP_NAME}' 앱을 사용해 휴대폰을 컨트롤러로\n사용할 수도 있습니다. 자세한 사항은 설정->컨트롤러를 참고하세요.", + "controllersInfoTextRemoteOnly": "당신은 네트워크를 이용하여 ${APP_NAME}을 친구들과 즐길 수 있고, 또는 당신은\n폰을 이용하여 '${REMOTE_APP_NAME}'앱을 깔아 \n같은 장치에서 다 같이 즐길 수 있습니다.", "controllersText": "컨트롤러", "controlsSubtitleText": "당신의 ${APP_NAME} 캐릭터는 약간의 기본적인 행동이 가능합니다", "controlsText": "컨트롤", @@ -1515,6 +1516,7 @@ "Slovak": "슬로바키아어", "Spanish": "스페인어", "Swedish": "스웨덴어", + "Thai": "태국어", "Turkish": "터키어", "Ukrainian": "우크라이나어", "Venetian": "베네토어", @@ -1563,6 +1565,7 @@ "Account linking successful!": "계정 연동 성공!", "Account unlinking successful!": "계정 연동 해제 완료!", "Accounts are already linked.": "계정들이 이미 연동되었습니다.", + "Ad view could not be verified.\nPlease be sure you are running an official and up-to-date version of the game.": "광고 시청 인증이 될수 없었습니다.\n공식 게임이고 최신 버전의 게임으로 실행하고 있도록 해주십시오.", "An error has occurred; (${ERROR})": "에러가 발생했습니다; (${ERROR})", "An error has occurred; please contact support. (${ERROR})": "심각한 에러가 발생했습니다.; 지원센터로 연락해주시오. (${ERROR})", "An error has occurred; please contact support@froemling.net.": "오류가 발생했습니다. support@froemling.net으로 문의해주십시오.", @@ -1588,6 +1591,7 @@ "Max number of profiles reached.": "최대 프로필 수에 도달했습니다.", "Maximum friend code rewards reached.": "최대의 친구 코드 보상에 도달했습니다.", "Message is too long.": "메시지가 너무 깁니다.", + "No servers are available. Please try again soon.": "가능한 서버가 없습니다. 나중에 다시 시도해주십시오.", "Profile \"${NAME}\" upgraded successfully.": "프로필 \"${NAME}\" 업그레이드 성공.", "Profile could not be upgraded.": "프로필을 업그레이드하지 못했습니다.", "Purchase successful!": "구매 성공!", @@ -1597,6 +1601,7 @@ "Sorry, this code has already been used.": "죄송합니다만 이 코드는 이미 사용되었습니다.", "Sorry, this code has expired.": "죄송합니다만 이 코드는 만료되었습니다.", "Sorry, this code only works for new accounts.": "죄송합니다만 이 코드는 새 계정에만 유효합니다.", + "Still searching for nearby servers; please try again soon.": "아직 근처에 있는 서버를 찾는 중입니다; 나중에 다시 시도해주십시오.", "Temporarily unavailable; please try again later.": "일시적으로 사용불가; 나중에 다시 시도하세요.", "The tournament ended before you finished.": "귀하가 완료하기 전에 토너먼트가 종료되었습니다.", "This account cannot be unlinked for ${NUM} days.": "이 계정은 ${NUM} 일 동안 연동 해제가 불가능합니다.", diff --git a/dist/ba_data/data/languages/persian.json b/dist/ba_data/data/languages/persian.json index efed99c..f647a8e 100644 --- a/dist/ba_data/data/languages/persian.json +++ b/dist/ba_data/data/languages/persian.json @@ -1,8 +1,8 @@ { "accountSettingsWindow": { - "accountNameRules": "نام نمی‌تواند اموجی (شکلک) یا نویسه‌های ویژه داشته باشد", + "accountNameRules": "نام می‌تواند اموجی (شکلک) یا نویسه‌های ویژه داشته باشد", "accountProfileText": "(مشخصات حساب)", - "accountsText": "حساب‌ها", + "accountsText": "پروفایل ها", "achievementProgressText": "${TOTAL} از‎ ${COUNT} :دستاوردها", "campaignProgressText": "${PROGRESS} :[سخت]‎ پیشروی در بازی اصلی", "changeOncePerSeason": ".فقط یک‌بار در هر فصل می‌توانید این مورد را تغییر دهید", @@ -11,10 +11,10 @@ "linkAccountsEnterCodeText": "کد را وارد کنید", "linkAccountsGenerateCodeText": "ایجاد کد", "linkAccountsInfoText": "(به اشتراک گذاری پیشروی بین دستگاه‌های مختلف)", - "linkAccountsInstructionsNewText": "برای اتصال دو حساب کاربری، ابتدا یک کد در حساب اول بسازید\nسپس آن را در حساب دوم وارد کنید. پس از این کار \n.اطلاعات حساب دوم بین دو حساب به اشتراک گذاشته می‌شود\n(اطلاعات حساب اول از بین خواهد رفت)\n\n.حساب را به هم متصل کنید‎ ${COUNT} شما می‌توانید تا\n\nتوجه: تنها حساب‌هایی که متعلق به خودتان است را\nبه هم متصل کنید! اگر به حساب دوستتان متصل\n.شوید، نمی‌توانید همزمان آنلاین بازی کنید", + "linkAccountsInstructionsNewText": "برای اتصال دو پروفایل، ابتدا یک کد در پروفایل اول بسازید\nسپس آن را در پروفایل دوم وارد کنید. پس از این کار \n.اطلاعات پروفایل دوم بین دو پروفایل به اشتراک گذاشته می‌شود\n(اطلاعات پروفایل اول از بین خواهد رفت)\n\n.پروفایل را به هم متصل کنید‎ ${COUNT} شما می‌توانید تا\nتوجه: تنها پروفایل‌هایی که مال خودتان است را\nبه هم متصل کنید! اگر به پروفایل دوستانتان وصل\n.شوید، نمی‌توانید همزمان آنلاین بازی کنید", "linkAccountsInstructionsText": "برای اتصال دو حساب، در یکی از\nآن ها کدی ایجاد کرده \nو آن را در دیگری وارد کنید.\nپیشرفت ها و موجودی ترکیب خواهد شد.\nحساب را وصل کنید ${COUNT} شما می توانید.\n!توجه : فقط حساب هایی را وصل کنید که برای\n شماست\nاگر شما حساب دیگری را وصل کنید، شما توانایی این را ندارید که در یک زمان بازی کنید!\nاین عمل برگشت پذیر نیست، پس \nدقت کنید!", - "linkAccountsText": "متصل کردن حساب‌ها", - "linkedAccountsText": "حساب‌های متصل‌شده:‎", + "linkAccountsText": "متصل کردن پروفایل‌ها", + "linkedAccountsText": "پروفایل‌های متصل‌شده:‎", "nameChangeConfirm": "تغییر کند؟‎ ${NAME} آیا نام شما به", "resetProgressConfirmNoAchievementsText": "همهٔ پیشروی‌های شما در بخش همکاری و بالاترین امتیازات\nشما پاک خواهد شد. (به استثنای بلیت‌های شما)\nاین کار برگشت‌پذیر نیست. آیا مطمئنید؟", "resetProgressConfirmText": "همهٔ پیشروی‌ها در بخش همکاری، دستاوردها\n.و امتیازات بالای شما پاک خواهد شد\n(به استثنای بلیت‌های شما)\nاین کار برگشت‌پذیر نیست. آیا مطمئنید؟", @@ -121,7 +121,7 @@ "name": "بازی با مین" }, "Off You Go Then": { - "description": "سه حریف رو از نقشه بنداز پایین", + "description": "سه حریف رو از زمین بنداز بیرون", "descriptionComplete": "سه حریف رو از نقشه انداختی پایین", "descriptionFull": "از نقشه بنداز پایین${LEVEL}سه حریف رو در مرحله ی", "descriptionFullComplete": "از نقشه انداختی پایین ${LEVEL} سه حریف رو در مرحله ی", @@ -132,7 +132,7 @@ "descriptionComplete": "پنج هزار امتیاز گرفتی", "descriptionFull": "بگیر${LEVEL}پنج هزار امتیاز در مرحله ی", "descriptionFullComplete": "گرفتی${LEVEL}پنج هزار امتیاز در مرحله ی", - "name": "${LEVEL} سَرور" + "name": "${LEVEL} خدا" }, "Onslaught Master": { "description": "پونصد امتیاز بگیر", @@ -223,7 +223,7 @@ "descriptionComplete": "دو هزار امتیاز گرفتی", "descriptionFull": "دو هزار امتیاز بگیر ${LEVEL} در مرحله", "descriptionFullComplete": "دو هزار امتیاز گرفتی ${LEVEL} در مرحله", - "name": "${LEVEL} سَرور" + "name": "${LEVEL} خدا" }, "Runaround Master": { "description": "پانصد امتیاز بگیر", @@ -818,6 +818,7 @@ "bombInfoText": "- بمب -\nقوی تر از مشته امامیتونه\n.برای خودتون هم خطرناک باشه\nدر بهترین زمان ممکن قبل از اینکه\n.در دست خودتون بترکه، پرتش بدید", "canHelpText": "میتونه به شما کمک کنه${APP_NAME}", "controllersInfoText": "بازی کنید و یا${APP_NAME}میتوانید توسط شبکه با دوستانتان\n.روی یک دستگاه بازی کنید اگر به اندازه ی کافی دسته دارید\nاز این تنوع پشتیبانی میکند؛ شما حتی میتوانید${APP_NAME}\nاز گوشی های هوشمند به عنوان دسته استفاده کنید از طریق برنامه\n.برای اطلاعات بیشتر به تنظیمات>کنترلرها بروید.${REMOTE_APP_NAME}", + "controllersInfoTextRemoteOnly": "شما میتونید ${APP_NAME} رو همراه دوستانتان به صورت اینترنتی ، یا\nبا استفاده از نرم‌افزار '${REMOTE_APP_NAME}' گوشیتون رو\nبه دسته بازی تبدیل کنید تا همه با هم در یک گوشی بازی کنید.", "controllersText": "کنترلرها", "controlsSubtitleText": ":شما چندتا حرکت اساسی داره ${APP_NAME} بازیکن", "controlsText": "کنترل‌ها", @@ -1518,6 +1519,7 @@ "Slovak": "اسلوواکی", "Spanish": "اسپانیایی", "Swedish": "سوئدی", + "Thai": "تایلندی", "Turkish": "ترکی", "Ukrainian": "اوکراینی", "Venetian": "ونیزی", @@ -1566,6 +1568,7 @@ "Account linking successful!": "ارتباط موفق با حساب کاربری", "Account unlinking successful!": "قطع شدن حساب با موفقیت انجام شد", "Accounts are already linked.": "حساب‌ها قبلا مرتبط شده‌اند", + "Ad view could not be verified.\nPlease be sure you are running an official and up-to-date version of the game.": "نمای آگهی تأیید نمی شود\nلطفا مطمئن باشید که نسخه رسمی و به روز بازی را اجرا می کنید", "An error has occurred; (${ERROR})": "متاسفانه یک مشکل رخ داده ؛ (${ERROR})", "An error has occurred; please contact support. (${ERROR})": "یک مشکل رخ داده! لطفا با پشتیبانی تماس بگیرید؛ (${ERROR})", "An error has occurred; please contact support@froemling.net.": ".تماس بگیرید support@froemling.net خطایی رخ داده است. لطفاً با", @@ -1591,6 +1594,7 @@ "Max number of profiles reached.": ".تعداد نمایه‌ها به حداکثر رسیده است", "Maximum friend code rewards reached.": ".حداکثر جایزه کد ارسالی برای دوستان دریافت شد", "Message is too long.": "پیام خیلی طولانی است", + "No servers are available. Please try again soon.": "هیچ سروری در دسترس نیست. لطفا به زودی دوباره امتحان کنید", "Profile \"${NAME}\" upgraded successfully.": ".با موفقیت ارتقا یافت «${NAME}» نمایهٔ", "Profile could not be upgraded.": ".نمایه نمی‌تواند ارتقا یابد", "Purchase successful!": "خرید با موفقیت انجام شد", @@ -1600,6 +1604,7 @@ "Sorry, this code has already been used.": "با عرض پوزش، این کد قبلا استفاده شده است.", "Sorry, this code has expired.": "متاسفانه این کد منقضی شده", "Sorry, this code only works for new accounts.": "با عرض وزش پوزش, این کد فقط برا حساب کاربری جدید کاربرد داره", + "Still searching for nearby servers; please try again soon.": "هنوز سرورهای اطراف را جستجو می کنید. لطفا به زودی دوباره امتحان کنید", "Temporarily unavailable; please try again later.": "در حال حاضر این گذینه موجود نمی باشد؛لطفا بعدا امتحان کنید", "The tournament ended before you finished.": "مسابقات به پایان رسید قبل از اینکه شما به پایان برسید.", "This account cannot be unlinked for ${NUM} days.": "این حساب برای مدت ${NUM} روز قابل جداسازی نیست!", diff --git a/dist/ba_data/data/languages/polish.json b/dist/ba_data/data/languages/polish.json index 89f505c..f3ecd95 100644 --- a/dist/ba_data/data/languages/polish.json +++ b/dist/ba_data/data/languages/polish.json @@ -63,7 +63,7 @@ }, "Dual Wielding": { "descriptionFull": "Podłącz dwa kontrolery (fizyczne lub BSRemote)", - "descriptionFullComplete": "Podłączone dwa kontrolery (fizyczne lub BSRemote)", + "descriptionFullComplete": "Podłączono dwa kontrolery (fizyczne lub BSRemote)", "name": "Podwójne dzierżenie" }, "Flawless Victory": { @@ -79,8 +79,8 @@ "name": "Łącznik graczy" }, "Gold Miner": { - "description": "Zabij 6 złych gości z użyciem min lądowych", - "descriptionComplete": "Zabiłeś 6 złych gości z użyciem min lądowych", + "description": "Zabij 6 złych gości minami lądowymi.", + "descriptionComplete": "Zabiłeś 6 złych gości minami lądowymi.", "descriptionFull": "Zabij 6 złych gości za pomocą min lądowych w trybie ${LEVEL}", "descriptionFullComplete": "Zabiłeś 6 złych gości za pomocą min lądowych w trybie ${LEVEL}", "name": "Złoty Saper" @@ -384,7 +384,7 @@ }, "configGamepadSelectWindow": { "androidNoteText": "Uwaga: wsparcie kontrolera uzależnione jest od urządzenia i wersji Androida.", - "pressAnyButtonText": "Naciśnij dowolny przycisk kontrolera\njeśli chcesz go skonfigurować...", + "pressAnyButtonText": "Naciśnij dowolny na kontrolerze,\nktórego chcesz skonfigurować...", "titleText": "Skonfiguruj Kontrolery" }, "configGamepadWindow": { @@ -401,7 +401,7 @@ "extraStartButtonText": "Dodatkowy przycisk Start", "ifNothingHappensTryAnalogText": "Jeśli nic się nie dzieje, spróbuj przypisać zamiast drążka analogowego.", "ifNothingHappensTryDpadText": "Jeśli nic się nie dzieje, spróbuj przypisać zamiast d-pada.", - "ignoreCompletelyDescriptionText": ".", + "ignoreCompletelyDescriptionText": "(uniemożliw wpływ tego kontrolera na grę lub menu)", "ignoreCompletelyText": "Ignoruj całkowicie", "ignoredButton1Text": "Pomijany przycisk 1", "ignoredButton2Text": "Pomijany przycisk 2", @@ -434,7 +434,7 @@ }, "configKeyboardWindow": { "configuringText": "Konfiguracja: ${DEVICE}", - "keyboard2NoteText": "Uwaga: większość klawiatur pozwala na jednoczesne naciśnięcie\ntylko kilku klawiszy. Lepszym rozwiązaniem będzie podłączenie\ndodatkowej klawiatury. Pamietać należy o tym, że w obydwu\nprzypadkach trzeba przypisać klawisze dla obydwu graczy." + "keyboard2NoteText": "Uwaga: większość klawiatur pozwala na\njednoczesne naciśnięcie tylko kilku klawiszy.\nLepszym rozwiązaniem będzie podłączenie dodatkowej klawiatury.\nPamiętać należy o tym, że w obydwu przypadkach\ntrzeba przypisać klawisze dla obydwu graczy." }, "configTouchscreenWindow": { "actionControlScaleText": "Skala przycisków akcji", @@ -455,9 +455,9 @@ "configureText": "Skonfiguruj", "connectMobileDevicesWindow": { "amazonText": "Sklep Amazon", - "appStoreText": "Sklep z aplikacjami", + "appStoreText": "App Store", "bestResultsText": "Dla lepszych efektów stwórz szybką sieć bezprzewodową.\nMożesz zredukować opóźnienia w grze poprzez: wyłączenie innych\nurządzeń korzystających w czasie gry z sieci wifi, będąc\nodpowiednio blisko routera wifi lub podpięcie się do hosta\nbezpośrednio przewodem sieciowym.", - "explanationText": "Aby użyć smartfona lub tableta jako kontrolera w grze, zainstaluj w nich \naplikację ${REMOTE_APP_NAME}. Do gry ${APP_NAME} można przyłączyć\n dowolną ilość urządzeń poprzez sieć WiFi i to całkowicie za darmo!", + "explanationText": "Aby użyć smartfona lub tableta jako kontrolera w grze,\nzainstaluj na nim aplikację ${REMOTE_APP_NAME}. Do gry ${APP_NAME} można\nprzyłączyć dowolną ilość urządzeń poprzez sieć WiFi i to całkowicie za darmo!", "forAndroidText": "dla Androida:", "forIOSText": "dla iOS:", "getItForText": "Pobierz ${REMOTE_APP_NAME} dla systemu iOS ze sklepu Apple, a \ndla systemu Android ze sklepu Google Play lub Amazon Appstore.", @@ -472,7 +472,7 @@ "activenessInfoText": "Ten mnożnik wzrasta w dniach, kiedy grasz\ni spada w dni, kiedy nie grasz.", "activityText": "Aktywność", "campaignText": "Kampania", - "challengesInfoText": "Zdobywaj nagrody za wykonywanie mini-gier.\n\nNagrody i poziomy trudności wzrastają za każdym razem kiedy wyzwanie jest\nukończone i \nzmniejszają kiedy wygasa bądź jest umorzone", + "challengesInfoText": "Zdobywaj nagrody za wykonywanie mini-gier.\n\nNagrody i poziomy trudności wzrastają\nza każdym razem kiedy wyzwanie jest ukończone i \nzmniejszają kiedy wygasa bądź jest umorzone.", "challengesText": "Wyzwania", "currentBestText": "Obecnie Najlepszy", "customText": "Własne", @@ -486,7 +486,7 @@ "ofTotalTimeText": "z ${TOTAL}", "playNowText": "Zagraj teraz", "pointsText": "Punkty", - "powerRankingFinishedSeasonUnrankedText": "(zakończony sezon,poza rankingiem)", + "powerRankingFinishedSeasonUnrankedText": "(sezon zakończony, poza rankingiem)", "powerRankingNotInTopText": "(nie jesteś na liście top ${NUMBER})", "powerRankingPointsEqualsText": "= ${NUMBER} pkt", "powerRankingPointsMultText": "(x ${NUMBER} pkt)", @@ -501,7 +501,7 @@ "titleText": "Kooperacja", "toRankedText": "Do awansu", "totalText": "Suma", - "tournamentInfoText": "Graj o wysokie wyniki z innymi graczami z twojej ligi.\n\nNagrody dostają gracze\nz najlepszymi wynikami\nkiedy zawody się kończą.", + "tournamentInfoText": "Graj o wysokie wyniki z\ninnymi graczami z twojej ligi.\n\nNagrody dostają gracze z najlepszymi\nwynikami kiedy zawody się kończą.", "welcome1Text": "Witaj w ${LEAGUE}. Możesz podnieść swój ligowy\nranking zdobywając gwiazdki, kompletując osiągnięcia\ni wygrywając trofea w turniejach.", "welcome2Text": "Możesz również zdobywać kupony z wielu tych samych działań.\nKupony mogą zostać użyte do: odblokowywania nowych postaci,\nmap, mini-gierek, uczestniczenia w turniejach i innych.", "yourPowerRankingText": "Twoje miejsce:" @@ -596,7 +596,7 @@ "globalProfileText": "(Profil globalny)", "highlightText": "kolor 2", "iconText": "Ikonka", - "localProfileInfoText": "Lokalny profil gracza nie możemieć ikonki i nie ma\n gwarancji, że takiej nazwy jeszcze nie ma.\nUlepsz do profilu globalnego aby stworzyć unikalną nazwę gracza i dodać ikonkę.", + "localProfileInfoText": "Lokalny profil gracza nie może mieć ikonki i nie ma\ngwarancji, że takiej nazwy jeszcze nie ma. Ulepsz do profilu globalnego\naby stworzyć unikalną nazwę gracza i dodać ikonkę.", "localProfileText": "(lokalny profil)", "nameDescriptionText": "Nazwa gracza", "nameText": "Nazwa", @@ -642,7 +642,7 @@ "enjoyText": "Miłej zabawy!", "epicDescriptionFilterText": "${DESCRIPTION} Epickie zwolnione tempo.", "epicNameFilterText": "Epicki tryb - ${NAME}", - "errorAccessDeniedText": "dostęp zabroniony", + "errorAccessDeniedText": "odmowa dostępu", "errorOutOfDiskSpaceText": "brak miejsca na dysku", "errorText": "Błąd", "errorUnknownText": "nieznany błąd", @@ -737,7 +737,7 @@ "googlePlayVersionOnlyText": "(Tylko Android / Google Play)", "hostPublicPartyDescriptionText": "Hostuj imprezę publiczną", "hostingUnavailableText": "Hostowanie niedostępne", - "inDevelopmentWarningText": "Uwaga:\n\nOpcja gry sieciowej jest nowa i będąca w fazie\nrozwojowej. Od teraz mocno zalecane jest aby\nwszyscy gracze byli w tej samej sieci (Wi-Fi lub LAN).", + "inDevelopmentWarningText": "Uwaga:\n\nOpcja gry sieciowej jest nowa i będąca w fazie\nrozwojowej. Na razie mocno zalecane jest aby\nwszyscy gracze byli w tej samej sieci (Wi-Fi lub LAN).", "internetText": "Internet", "inviteAFriendText": "Znajomi nie mają gry?\nZaproś ich do sprawdzenia a oni otrzymają ${COUNT} darmowych kuponów.", "inviteFriendsText": "Zaproś przyjaciół", @@ -763,7 +763,7 @@ "partyCodeText": "Kod imprezy", "partyInviteAcceptText": "Akceptuj", "partyInviteDeclineText": "Ignoruj", - "partyInviteGooglePlayExtraText": "(zobacz zakładkę 'Google Play' w oknie 'Punkt Zbiorny')", + "partyInviteGooglePlayExtraText": "(zobacz zakładkę 'Google Play' w oknie 'Punkt Zborny')", "partyInviteIgnoreText": "Ignoruj", "partyInviteText": "${NAME} zaprosił Cię abyś\ndołączył do ich imprezy.", "partyNameText": "Nazwa Imprezy", @@ -791,8 +791,8 @@ "startHostingText": "Hostuj", "startStopHostingMinutesText": "Możesz rozpocząć i zakończyć hostowanie za darmo przez następne ${MINUTES} minut.", "stopHostingText": "Zakończ hostowanie", - "titleText": "Punkt Zbiorny", - "wifiDirectDescriptionBottomText": "Jeśli wszystkie urządzenia posiadają panel 'Wi-Fi Direct', to powinny użyć go aby\nodnaleźć i połączyć się między sobą. Kiedy wszystkie są już połączone, możesz utworzyć\nimprezę używając zakładki 'Lokalna sieć', tak samo jak w standardowej sieci Wi-Fi.\n\nDla optymalnego działania, host Wi-Fi Direct powinien być hostem zabawy w ${APP_NAME}.", + "titleText": "Punkt Zborny", + "wifiDirectDescriptionBottomText": "Jeśli wszystkie urządzenia posiadają panel 'Wi-Fi Direct', to powinny użyć go aby\nodnaleźć i połączyć się między sobą. Kiedy wszystkie są już połączone, możesz utworzyć\nimprezę używając zakładki 'Lokalna sieć', tak samo jak w standardowej sieci Wi-Fi.\n\nDla optymalnego działania, host Wi-Fi Direct powinien być hostem imprezy w ${APP_NAME}.", "wifiDirectDescriptionTopText": "Wi-Fi Direct może być używany do bezpośredniego łączenia urządzeń na\nAndroidzie bez konieczności stosowania sieci Wi-Fi. Najlepiej działa na\nurządzeniach z systemem Android 4.2 lub nowszym.\nAby go użyć, otwórz ustawienia Wi-Fi urządzenia i odszukaj 'Wi-Fi Direct'.", "wifiDirectOpenWiFiSettingsText": "Otwórz ustawienia Wi-Fi", "wifiDirectText": "Wi-Fi Direct", @@ -808,7 +808,7 @@ "titleText": "Zdobądź monety" }, "getTicketsWindow": { - "freeText": "DARMO!", + "freeText": "DARMOWE!", "freeTicketsText": "Darmowe kupony", "inProgressText": "Transakcja w toku; proszę spróbować za chwilkę.", "purchasesRestoredText": "Zakupy przywrócone.", @@ -858,6 +858,7 @@ "controllersInfoTextFantasia": "Gracz może używać zdalnego kontrolera, jednak zalecane są\ngamepady. Możesz także użyć urządzeń mobilnych jako kontrolerów\ngry za pomocą darmowej aplikacji 'BombSquad Remote'.\nSprawdź informacje dostępne w ustawieniach kontrolerów.", "controllersInfoTextMac": "Jeden lub dwóch graczy może używać klawiatury, jednak najlepiej korzystać z\ngamepadów. Gra obsługuje pady USB, kontrolery PS3, Xbox360, Wiimote i urządzenia\nz systemem iOS/Android. Na pewno coś z tego posiadasz aby sterować postaciami?\nWięcej informacji dostępnych jest w ustawieniach kontrolerów.", "controllersInfoTextOuya": "Do gry w BombSquad możesz wykorzystać kontrolery OUYA, PS3, Xbox360\ni wiele innych gamepadów podłączanych za pomocą USB lub Bluetootha.\nMożesz również używać jako kontrolery urządzenia z systemami iOS/Android\nz pomocą darmowej aplikacji 'BombSquad Remote'. Więcej informacji w\nustawieniach kontrolerów.", + "controllersInfoTextRemoteOnly": "Możesz grać w ${APP_NAME} ze znajomymi przez sieć lub\nna tym samym urządzeniu, używając telefonów jako\nkontrolerów dzięki bezpłatnej aplikacji \"${REMOTE_APP_NAME}\".", "controllersText": "Kontrolery", "controlsSubtitleText": "Twoja postać w ${APP_NAME} posiada kilka podstawowych umiejętności:", "controlsText": "Przyciski", @@ -991,7 +992,7 @@ "fullMenuText": "Pełne Menu", "hardText": "Trudny", "mediumText": "Średni", - "singlePlayerExamplesText": "Przykłady trybu Pojedyńczego Gracza / Kooperacji", + "singlePlayerExamplesText": "Przykłady trybu Pojedynczego Gracza / Kooperacji", "versusExamplesText": "Przykłady trybu Versus" }, "languageSetText": "Obecny język gry to \"${LANGUAGE}\".", @@ -1092,7 +1093,7 @@ "notEnoughPlayersRemainingText": "Niewystarczająca ilość graczy. Spróbuj zacząć nową grę.", "notEnoughPlayersText": "Aby rozpocząć grę potrzeba ${COUNT} graczy!", "notNowText": "Nie teraz", - "notSignedInErrorText": "Musisz zalogować się, aby to zrobić", + "notSignedInErrorText": "Musisz zalogować się, aby to zrobić.", "notSignedInGooglePlayErrorText": "Zaloguj się z Google Play, by to zrobić.", "notSignedInText": "Nie zapisany", "nothingIsSelectedErrorText": "Nic nie zaznaczyłeś!", @@ -1201,7 +1202,7 @@ "readyText": "gotowy", "recentText": "Ostatnie", "remainingInTrialText": "pozostań w wersji trial", - "remoteAppInfoShortText": "${APP_NAME} jest najfajniejszy gdy grasz z rodziną i przyjaciółmi. \nPodłącz jeszcze jeden lub więcej kontrolerów lub zainstaluj \n${REMOTE_APP_NAME} na telefony lub tablety i używaj ich\n jako kontrolerów.", + "remoteAppInfoShortText": "${APP_NAME} jest najfajniejszy gdy grasz z rodziną i przyjaciółmi. \nPodłącz jeszcze jeden lub więcej kontrolerów lub zainstaluj \n${REMOTE_APP_NAME} na telefony lub tablety i używaj ich\njako kontrolerów.", "remote_app": { "app_name": "BombSquad Remote", "app_name_short": "BSRemote", @@ -1210,7 +1211,7 @@ "cant_resolve_host": "Nie można zanalizować hosta.", "capturing": "Przechwytywanie...", "connected": "Połączony.", - "description": "Użyj telefonu lub tabletu jako kontrolera w BombSquad.\nDo 8 urządzeń może być połączone naraz w epickiej wieloosobowej grze na jednym telewizorze lub tablecie.", + "description": "Użyj telefonu lub tabletu jako kontrolera w BombSquad.\nDo 8 urządzeń może być połączone na raz w epickiej wieloosobowej grze na jednym telewizorze lub tablecie.", "disconnected": "Rozłączono przez serwer.", "dpad_fixed": "nieruchomy", "dpad_floating": "ruchomy", @@ -1301,7 +1302,7 @@ "showPlayerNamesText": "Pokazuj nazwy graczy", "showUserModsText": "Pokaż katalog modów", "titleText": "Zaawansowane", - "translationEditorButtonText": "Edytor tłumaczący ${APP_NAME}", + "translationEditorButtonText": "Edytor tłumaczeń ${APP_NAME}", "translationFetchErrorText": "status tłumaczenia niedostępny", "translationFetchingStatusText": "sprawdzanie statusu tłumaczenia...", "translationInformMe": "Powiadom mnie gdy mój język będzie potrzebował uaktualnienia", @@ -1352,7 +1353,7 @@ "charactersText": "Postacie", "comingSoonText": "Wkrótce...", "extrasText": "Dodatki", - "freeBombSquadProText": "BombSquad jest teraz darmowy, ale kiedy go oficjalnie zakupisz wówczas\notrzymasz wersję BombSquad Pro i ${COUNT} kuponów jako wyraz wdzięczności.\nMiłego korzystania z nowych funkcji i dziękuję za wsparcie!\n-Eric", + "freeBombSquadProText": "BombSquad jest teraz darmowy, ale od kiedy oficjalnie go zakupiłeś\notrzymujesz wersję BombSquad Pro i ${COUNT} kuponów jako wyraz wdzięczności.\nMiłego korzystania z nowych funkcji i dziękuję za wsparcie!\n-Eric", "gameUpgradesText": "Aktualizacje Gry", "getCoinsText": "Zdobądź monety", "holidaySpecialText": "Świąteczne Okazje", @@ -1598,6 +1599,7 @@ "Slovak": "słowacki", "Spanish": "Hiszpański", "Swedish": "Szwedzki", + "Thai": "Tajski", "Turkish": "Turecki", "Ukrainian": "Ukraiński", "Venetian": "Wenecki", @@ -1649,6 +1651,7 @@ "Account linking successful!": "Łączenie kont zakończone sukcesem!", "Account unlinking successful!": "Pomyślnie rozłączono konta!", "Accounts are already linked.": "Konta są już połączone.", + "Ad view could not be verified.\nPlease be sure you are running an official and up-to-date version of the game.": "Nie można zweryfikować widoku reklamy.\nUpewnij się, że korzystasz z oficjalnej i aktualnej wersji gry.", "An error has occurred; (${ERROR})": "Wystąpił błąd; (${ERROR})", "An error has occurred; please contact support. (${ERROR})": "Wystąpił błąd; skontaktuj się z pomocą techniczną. (${ERROR})", "An error has occurred; please contact support@froemling.net.": "Wystąpił błąd; skontaktuj się z support@froemling.net.", @@ -1668,14 +1671,15 @@ "Invalid purchase.": "Nieprawidłowy zakup.", "Invalid tournament entry; score will be ignored.": "Nieprawidłowe wejście do turnieju; wynik będzie zignorowany.", "Item unlocked!": "Przedmiot odblokowany!", - "LINKING DENIED. ${ACCOUNT} contains\nsignificant data that would ALL BE LOST.\nYou can link in the opposite order if you'd like\n(and lose THIS account's data instead)": "ŁĄCZENIE ODRZUCONE. ${ACCOUNT} zawiera\nznaczący postęp który zostanie USUNIĘTY.\nMożecie połączyć konta na odwrót jeśli chcecie\n(i usunąć postęp tego DRUGIEGO konta).", + "LINKING DENIED. ${ACCOUNT} contains\nsignificant data that would ALL BE LOST.\nYou can link in the opposite order if you'd like\n(and lose THIS account's data instead)": "ŁĄCZENIE ODRZUCONE. ${ACCOUNT} zawiera\nznaczący postęp który zostałby USUNIĘTY.\nMożesz połączyć konta na odwrót jeśli chcesz\n(i stracić postęp Z TEGO konta).", "Link account ${ACCOUNT} to this account?\nAll existing data on ${ACCOUNT} will be lost.\nThis can not be undone. Are you sure?": "Połączyć konto ${ACCOUNT} z tym kontem?\nCały postęp z konta ${ACCOUNT} będzie stracony.\nNie można tego odwrócić. Pewna decyzja?", "Max number of playlists reached.": "Osiągnięto maksymalną ilość playlist.", "Max number of profiles reached.": "Osiągnięto maksymalną liczbę profili.", "Maximum friend code rewards reached.": "Osiągnięto limit kodów promocyjnych.", "Message is too long.": "Wiadomość jest za długa.", + "No servers are available. Please try again soon.": "Brak dostępnych serwerów. Spróbuj ponownie wkrótce.", "Profile \"${NAME}\" upgraded successfully.": "Nazwa \"${NAME}\" ulepszona pomyślnie.", - "Profile could not be upgraded.": "Profil nie może być zmieniony.", + "Profile could not be upgraded.": "Profil nie może być ulepszony.", "Purchase successful!": "Udany zakup!", "Received ${COUNT} tickets for signing in.\nCome back tomorrow to receive ${TOMORROW_COUNT}.": "Otrzymano ${COUNT} kuponów za zapisanie się. Wróć jutro aby otrzymać\n${TOMORROW_COUNT}.", "Server functionality is no longer supported in this version of the game;\nPlease update to a newer version.": "Funkcje serwerowe nie są dalej wspierane na tej wersji gry;\nZaktualizuj grę i spróbuj ponownie.", @@ -1683,6 +1687,7 @@ "Sorry, this code has already been used.": "Przepraszamy, ten kod został już użyty.", "Sorry, this code has expired.": "Przepraszamy, ten kod wygasł.", "Sorry, this code only works for new accounts.": "Przepraszamy, ten kod działa tylko na nowych kontach.", + "Still searching for nearby servers; please try again soon.": "Wciąż szukam pobliskich serwerów; proszę spróbuj ponownie wkrótce.", "Temporarily unavailable; please try again later.": "Tymczasowo niedostępne; spróbuj ponownie później.", "The tournament ended before you finished.": "Wyniki po zakończonym turnieju.", "This account cannot be unlinked for ${NUM} days.": "To konto nie może zostać rozłączone przez ${NUM} dni.", @@ -1746,14 +1751,14 @@ "Score to Win": "Punktów do zwycięstwa", "Short": "Krótki", "Shorter": "Krótszy", - "Solo Mode": "Tryb pojedyńczego gracza", + "Solo Mode": "Tryb pojedynczego gracza", "Target Count": "Liczba docelowa", "Time Limit": "Limit czasowy" }, "statements": { "${TEAM} is disqualified because ${PLAYER} left": "${TEAM} zostali zdyskwalifikowani, gdyż ${PLAYER} wyszedł", "Killing ${NAME} for skipping part of the track!": "Zabito ${NAME} za pominięcie części toru!", - "Warning to ${NAME}: turbo / button-spamming knocks you out.": "Ostrzeżenie dla ${NAME}: turbo/spam kontrolkami nokautuje Cię." + "Warning to ${NAME}: turbo / button-spamming knocks you out.": "Ostrzeżenie dla ${NAME}: turbo / spam kontrolkami nokautuje Cię." }, "teamNames": { "Bad Guys": "Źli goście", @@ -1781,7 +1786,7 @@ "If you pick up a curse, your only hope for survival is to\nfind a health powerup in the next few seconds.": "Jeśli zbierzesz 'Klątwę', to jedyną nadzieją aby\nprzetrwać jest szybkie zebranie apteczki.", "If you stay in one place, you're toast. Run and dodge to survive..": "Jeśli będziesz się czaił w jednym miejscu to jesteś usmażony.\nBiegaj i unikaj ataków aby przetrwać.", "If you've got lots of players coming and going, turn on 'auto-kick-idle-players'\nunder settings in case anyone forgets to leave the game.": "Jeśli doświadczasz dużej rotacji wśród graczy, najlepiej włącz 'auto wyrzucanie\nbezczynnych graczy' w ustawieniach. Wyrzuci to tych, którzy nie grają a jedynie\nwiszą w grze blokując nowych chcących zagrać.", - "If your device gets too warm or you'd like to conserve battery power,\nturn down \"Visuals\" or \"Resolution\" in Settings->Graphics": "Jeśli Twoje urządzenie mocno się przegrzewa powinieneś oszczędzić baterię\nwyłączając 'Wizualizacje' lub zmniejszyć 'Rozdzielczość' w Ustawienia->Grafika", + "If your device gets too warm or you'd like to conserve battery power,\nturn down \"Visuals\" or \"Resolution\" in Settings->Graphics": "Jeśli Twoje urządzenie mocno się przegrzewa lub po prostu chcesz oszczędzić baterię,\nwyłącz 'Wizualizacje' lub zmniejsz 'Rozdzielczość' w Ustawienia->Grafika", "If your framerate is choppy, try turning down resolution\nor visuals in the game's graphics settings.": "Jeśli ilość klatek na sekundę jest zbyt niska, spróbuj\nzmniejszyć rozdzielczość lub jakość ustawień graficznych.", "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.": "W trybie 'Przechwycenia Flagi', Twoja flaga musi znajdować się w bazie aby zapunktować.\nJeśli drugi zespół zamierza zdobyć punkt, przechwycenie ich flagi będzie dobrym\nrozwiązaniem aby ich powstrzymać.", "In hockey, you'll maintain more speed if you turn gradually.": "Grając w hokeja, większą prędkość utrzymywać będziesz przy\nstopniowym i delikatnym skręcaniu postacią.", @@ -1835,7 +1840,7 @@ "phrase14Text": "Możesz podnosić i rzucać np. flagami, bombami, a nawet przeciwnikiem - ${NAME}.", "phrase15Text": "Ale BombSquad to głównie BOMBY.", "phrase16Text": "Skuteczne rzucanie bombami wymaga odrobinę praktyki.", - "phrase17Text": "Jałć! Niezbyt dobry rzut.", + "phrase17Text": "Ałć! Niezbyt dobry rzut.", "phrase18Text": "Poruszanie się przy rzucie pozwala rzucać dalej.", "phrase19Text": "Skakanie pozwala rzucać wyżej.", "phrase20Text": "Kręć bombą, aby cisnąć nią dalej.", @@ -1853,7 +1858,7 @@ "randomName3Text": "Benio", "randomName4Text": "Czesio", "randomName5Text": "Ignaś", - "skipConfirmText": "Jeśli chcesz pominąć samouczek to stuknij lub naciśnij aby zatwierdzić.", + "skipConfirmText": "Naprawdę chcesz pominąć samouczek? Stuknij lub naciśnij aby zatwierdzić.", "skipVoteCountText": "${COUNT}/${TOTAL} pominiętych głosów", "skippingText": "pomijam samouczek...", "toSkipPressAnythingText": "(stuknij lub naciśnij cokolwiek aby pominąć samouczek)" @@ -1865,7 +1870,7 @@ "unlockThisProfilesText": "By stworzyć więcej niż ${NUM} kont, potrzebujesz:", "unlockThisText": "Żeby to odblokować, potrzebujesz:", "unsupportedHardwareText": "Przepraszam ale ten sprzęt nie jest obsługiwany przez tą wersję gry.", - "upFirstText": "Pierwsza gra w rozgrywce:", + "upFirstText": "Pierwsza gra:", "upNextText": "Kolejna, ${COUNT} gra w rozgrywce:", "updatingAccountText": "Aktualizowanie twojego konta...", "upgradeText": "Ulepsz", diff --git a/dist/ba_data/data/languages/portuguese.json b/dist/ba_data/data/languages/portuguese.json index 388e239..59bb5b9 100644 --- a/dist/ba_data/data/languages/portuguese.json +++ b/dist/ba_data/data/languages/portuguese.json @@ -868,6 +868,7 @@ "controllersInfoTextFantasia": "Um jogador pode usar o controle remoto como controle, mas controles para games\nsão altamente recomendados. Você também pode usar dispositivos móveis\ncomo controles usando o app gratuito 'BombSquad Remote'.\nVeja 'Controles' em 'Configurações' para mais informações.", "controllersInfoTextMac": "Um ou dois jogadores podem usar o teclado, mas BombSquad é melhor com gamepads.\nBombSquad pode usar gamepads USB, controles de PS3, controles de Xbox 360,\nWiimotes e dispositivos iOS/Android para controlar personagens. Esperamos que você tenha\nalguns desses acessíveis. Consulte 'Controles', em 'Configurações' para mais informações.", "controllersInfoTextOuya": "Você pode usar controles OUYA, controles PS3, controles Xbox 360,\ne muitos outros controles USB e Bluetooth com BombSquad.\nVocê também pode usar dispositivos iOS e Android como controles via app\ngratuito 'BombSquad Remote'. Veja 'Controles' em 'Configurações' para detalhes.", + "controllersInfoTextRemoteOnly": "Você pode jogar ${APP_NAME} com amigos na rede ou \ntodos podem jogar no mesmo dispositivo usando telefones como\ncontroladores através do aplicativo gratuito '${REMOTE_APP_NAME}'.", "controllersInfoTextScaleFantasia": 0.51, "controllersInfoTextScaleMac": 0.58, "controllersInfoTextScaleOuya": 0.63, @@ -1626,6 +1627,7 @@ "Slovak": "Eslovaco", "Spanish": "Espanhol", "Swedish": "Sueco", + "Thai": "Tailandês", "Turkish": "Turco", "Ukrainian": "Ucraniano", "Venetian": "Veneziano", @@ -1677,6 +1679,7 @@ "Account linking successful!": "A conta foi vinculada com êxito!", "Account unlinking successful!": "Conta desvinculada com êxito!", "Accounts are already linked.": "As contas já estão vinculadas.", + "Ad view could not be verified.\nPlease be sure you are running an official and up-to-date version of the game.": "Não foi possível verificar a exibição do anúncio.\nCertifique-se de que está executando uma versão oficial e atualizada do jogo.", "An error has occurred; (${ERROR})": "Ocorreu um erro; (${ERROR})", "An error has occurred; please contact support. (${ERROR})": "Ocorreu um erro; entre em contato com a assistência. (${ERROR})", "An error has occurred; please contact support@froemling.net.": "Ocorreu um erro; por favor, entre em contato com support@froemling.net.", diff --git a/dist/ba_data/data/languages/romanian.json b/dist/ba_data/data/languages/romanian.json index bd36ae1..f5baddd 100644 --- a/dist/ba_data/data/languages/romanian.json +++ b/dist/ba_data/data/languages/romanian.json @@ -755,9 +755,17 @@ "privatePartyCloudDescriptionText": "Petreceri private rulează pe servere cloud dedicate; nu este necesară configurarea routerului.", "privatePartyHostText": "Găzduiește o petrecere privată", "privatePartyJoinText": "Alăturați-vă unei petreceri private", + "privateText": "Privat", + "publicHostRouterConfigText": "Acest lucru poate necesita configurarea redirecționării porturilor pe router. Pentru o opțiune mai ușoară, găzduiește o petrecere privată.", + "publicText": "Public", "requestingAPromoCodeText": "Se cere codul...", "sendDirectInvitesText": "Trimite Invitați Direct", "shareThisCodeWithFriendsText": "Împărtăşeşte codul ăsta cu prietenii:", + "showMyAddressText": "Afișează adresa mea", + "startHostingPaidText": "Găzduiește acum pentru ${COST}", + "startHostingText": "Gazdă", + "startStopHostingMinutesText": "Puteți începe și opri găzduirea gratuit pentru următoarele ${MINUTES} minute.", + "stopHostingText": "Opriți găzduirea", "titleText": "Adunare", "wifiDirectDescriptionBottomText": "Dacă toate dispozitivele au un panou 'Wi-Fi Direct', ar trebui să poată să îl folosească să\nse găsească și să se conecteze unii la alții. Când toate dispozitivele sunt conectate se pot\nforma grupuri, aici, folosind tab-ul 'Rețea locală', ca și când ați fi pe aceiași rețea Wi-Fi.\n\nPentru cele mai bune rezultate, host-ul Wi-Fi Direct ar trebui să fie și host-ul grupului ${APP_NAME}.", "wifiDirectDescriptionTopText": "Wi-Fi direct se poate folosi pentru conectarea dispozitivelor Android fără\na folosi o rețea Wi-Fi. Aceasta funcționează (bine) pe Android 4.2 sau mai nou.\n\nPentru a folosi Wi-Fi direct, deschide setările Wi-Fi și caută 'Wi-Fi Direct' în meniu.", @@ -784,13 +792,14 @@ "ticketsFromASponsorText": "Ia ${COUNT} bilete de\nla un sponsor", "ticketsText": "${COUNT} Bilete", "titleText": "Ia bilete", - "unavailableLinkAccountText": "Scuze, dar cumpăratul nu funcționează pe această platformă.\nDacă dorești, poți conecta acest cont cu unul de\npe o altă platformă și să faci cumpărăturile acolo.", + "unavailableLinkAccountText": "Ne pare rău, achizițiile nu sunt disponibile pe această platformă.\nCa soluție, puteți conecta acest cont la un cont de pe\no altă platformă și faceți cumpărături acolo.", "unavailableTemporarilyText": "Acest serviciu e indisponibil deocamdată; încearcă mai târziu.", "unavailableText": "Scuze, aceasta e indisponibilă.", "versionTooOldText": "Scuze, dar versiunea jocului e prea veche; dă update pentru a lua una mai nouă.", "youHaveShortText": "tu ai ${COUNT}", "youHaveText": "ai ${COUNT} bilete" }, + "googleMultiplayerDiscontinuedText": "Ne pare rău, serviciul multiplayer Google nu mai este disponibil.\nLucrez la un înlocuitor cât mai repede posibil.\nPână atunci, vă rugăm să încercați o altă metodă de conectare.\n-Eric", "googlePlayText": "Magazin Play", "graphicsSettingsWindow": { "alwaysText": "Întotdeauna", @@ -813,11 +822,12 @@ "helpWindow": { "bombInfoText": "- Bomba -\nMai puternică decât pumnii, dar poate\nrezulta în a te lovi pe tine însuți.\nPentru rezultate pozitive, aruncă\nînspre inamici înainte să se termine fitilul.", "canHelpText": "${APP_NAME} poate ajuta.", - "controllersInfoText": "Poți juca BombSquad cu prietenii peste o rețea, sau dacă aveți\ndestule controllere, pe același dispozitiv. BombSquad suportă o\nmare varietate de controllere; Poți folosi până și telefoane\nca unul descărcând aplicația \"BombSquad remote\".\nVezi Setări->Controllere pentru mai multe informații.", + "controllersInfoText": "Puteți juca ${APP_NAME} cu prietenii dintr-o rețea sau dvs.\npot juca cu toții pe același dispozitiv dacă aveți suficiente controlere.\n${APP_NAME} acceptă o varietate de ele; puteți folosi chiar și telefoane\nca controlori prin intermediul aplicației gratuite „${REMOTE_APP_NAME}”.\nConsultați Setări-> Controlere pentru mai multe informații.", + "controllersInfoTextRemoteOnly": "Puteți juca ${APP_NAME} cu prietenii dintr-o rețea sau dvs.\ntoți pot reda pe același dispozitiv folosind telefoane ca\ncontrolere prin intermediul aplicației gratuite „${REMOTE_APP_NAME}”.", "controllersText": "Controllere", - "controlsSubtitleText": "Caracterul tău BombSquad poate face următoarele acțiuni:", + "controlsSubtitleText": "Caracterul dvs. prietenos ${APP_NAME} are câteva acțiuni de bază:", "controlsText": "Controluri", - "devicesInfoText": "Versiunea VR a jocului poate fi jucată peste rețea cu versiunea\nnormală, deci scoate telefoanele, tabletele și calculatoarele\nși să înceapă jocul! Poți conecta versiunea VR la cea normală\npentru a putea lăsa alte persoane să ia parte la acțiune ca\nși spectatori.", + "devicesInfoText": "Versiunea VR a ${APP_NAME} poate fi redată prin rețea cu\nversiunea obișnuită, așa că scoateți-vă telefoanele, tabletele,\nși computere și începe jocul. Poate fi chiar util\nconectați o versiune obișnuită a jocului la versiunea VR doar la\npermite oamenilor din exterior să urmărească acțiunea.", "devicesText": "Dispozitive", "friendsGoodText": "E bine să ai și de-aceștia. ${APP_NAME} e și mai amuzant cu mai\nmulți jucători și suportă până la 8 deodată, ce ne duce la:", "friendsText": "Prieteni", @@ -847,20 +857,29 @@ "punchInfoText": "- Pumnii -\nPumnii dăunează mai mult cu cât\nse mișcă mai repede, deci aleargă\nși rotește-te ca un dement!", "runInfoText": "- Fugi -\nȚine apăsat ORICE buton pentru a fugi. Triggerele sau butoanele de umăr funcționează dacă le ai.\nFugitul te ajută să ajungi repede în locuri, deși virezi greu, deci ai grijă la prăpastii.", "someDaysText": "În unele zile pur și simplu vrei să lovești ceva. Sau să explodezi altceva.", - "titleText": "Ajutor BombSquad", + "titleText": "${APP_NAME} Ajutor", "toGetTheMostText": "Pentru a vedea tot ce are jocul acesta, vei avea nevoie de:", - "welcomeText": "Bine ai venit la BombSquad!" + "welcomeText": "Bun venit la ${APP_NAME}!" }, "holdAnyButtonText": "<ține apăsat orice buton>", "holdAnyKeyText": "<ține apăsată orice tastă>", "hostIsNavigatingMenusText": "- ${HOST} navighează meniurile boss de boss -", + "importPlaylistCodeInstructionsText": "Utilizați următorul cod pentru a importa această listă de redare în altă parte:", + "importPlaylistSuccessText": "Lista de redare ${TYPE} importată „${NAME}”", + "importText": "Import", + "importingText": "Se importă ...", + "inGameClippedNameText": "în joc va fi\n„${NAME}”", "installDiskSpaceErrorText": "EROARE: Nu s-a putut completa instalarea.\nSe poate să fi rămas fără spațiu pe dispozitiv.\nEliberează niște spațiu și reîncearcă.", "internal": { "arrowsToExitListText": "Apasă ${LEFT} sau ${RIGHT} pentru a ieși din listă", "buttonText": "buton", + "cantKickHostError": "Nu poți da afară gazda.", + "chatBlockedText": "${NAME} este blocat prin chat timp de ${TIME} secunde.", + "connectedToGameText": "S-a înscris la „${NAME}”", "connectedToPartyText": "Ai intrat în grupul lui ${NAME}!", "connectingToPartyText": "Se conectează...", "connectionFailedHostAlreadyInPartyText": "Conexiunea a eşuat; hostul este în altă petrecere", + "connectionFailedPartyFullText": "Conexiune esuata; petrecerea este plină.", "connectionFailedText": "Conexiunea a eşuat.", "connectionFailedVersionMismatchText": "Conexiunea a eşuat; hostul rulează o versiune diferită a jocului.\nAsigurați-vă ca aveți amândoi cea mai nouă versiune a jocului şi incercați din nou.", "connectionRejectedText": "Conexiune Respinsă.", @@ -868,6 +887,7 @@ "controllerDetectedText": "1 controller detectat.", "controllerDisconnectedText": "${CONTROLLER} a ieşit.", "controllerDisconnectedTryAgainText": "${CONTROLLER} a ieşit. Încearcă să-l conectezi din nou.", + "controllerForMenusOnlyText": "Acest controler nu poate fi folosit pentru a juca; doar pentru a naviga prin meniuri.", "controllerReconnectedText": "${CONTROLLER} reconectat.", "controllersConnectedText": "${COUNT} controllere conectate.", "controllersDetectedText": "${COUNT} controllere detectate.", @@ -876,12 +896,15 @@ "errorPlayingMusicText": "Eroare la începerea muzicii 0: ${MUSIC}", "errorResettingAchievementsText": "Nu se pot reseta medaliile; încearcă din nou mai tărziu.", "hasMenuControlText": "${NAME} are controlul meniului.", + "incompatibleNewerVersionHostText": "Gazda rulează o versiune mai nouă a jocului.\nActualizați la cea mai recentă versiune și încercați din nou.", "incompatibleVersionHostText": "Hostul rulează o versiune diferită a jocului.\nAsigurați-vă că aveți cea mai nouă versiune şi încercați din nou.", "incompatibleVersionPlayerText": "${NAME} rulează o versiune diferită a jocului.\nAsigurați-vă că aveți cea mai nouă versiune si reîncercați.", "invalidAddressErrorText": "Eroare: adresă invalidă.", + "invalidNameErrorText": "Eroare: nume nevalid.", + "invalidPortErrorText": "Eroare: port nevalid.", "invitationSentText": "Invitație Trimisă.", "invitationsSentText": "${COUNT} (de) invitații trimise.", - "joinedPartyInstructionsText": "Un prieten a venit la petrecerea ta.\nDu-te la 'Joacă' pentru a începe un joc.", + "joinedPartyInstructionsText": "Cineva s-a alăturat partidului tău.\nAccesați „Joacă” pentru a începe un joc.", "keyboardText": "Tastatură", "kickIdlePlayersKickedText": "Îl dăm afară pe ${NAME} pentru că nu face nimic.", "kickIdlePlayersWarning1Text": "${NAME} va fi dat afară în ${COUNT} (de) secunde dacă tot nu face nimic.", diff --git a/dist/ba_data/data/languages/russian.json b/dist/ba_data/data/languages/russian.json index b05fd8e..d9977fe 100644 --- a/dist/ba_data/data/languages/russian.json +++ b/dist/ba_data/data/languages/russian.json @@ -864,6 +864,7 @@ "controllersInfoTextFantasia": "Один игрок может использовать пульт дистанционного управления,\nно рекомендуется использовать геймпад. Также вместо контроллеров\nможно использовать мобильные устройства с помощью\nбесплатного приложения 'BombSquad Remote'.\nДополнительную информацию см. в 'Настройки'>'Контроллеры'.", "controllersInfoTextMac": "Один-два игрока могут играть с клавиатуры, но BombSquad лучше всего работает\nс геймпадами. Управлять персонажами в BombSquad можно с помощью USB геймпадов,\nконтроллеров PS3, Xbox 360, Wii и устройств iOS/Android. Надеюсь, у вас есть такие\nпод рукой. Дополнительную информацию см. в разделе 'Настройки' > 'Контроллеры'.", "controllersInfoTextOuya": "С BombSquad можно использовать контроллеры OUYA, PS3, Xbox 360, а также\nмножество других USB и Bluetooth геймпадов. Также можно использовать\nустройства iOS и Android в качестве контроллеров через бесплатное приложение\n'BombSquad Remote'. Дополнительную информацию см. в 'Настройки' > 'Контроллеры'.", + "controllersInfoTextRemoteOnly": "Ты можешь играть ${APP_NAME} с друзьями по сети, или вы\nвсе можете играть на одном устройстве используя ваши смартфоны как\nконтроллеры через бесплатное приложение '${REMOTE_APP_NAME}'.", "controllersText": "Контроллеры", "controlsSubtitleText": "У вашего дружелюбного персонажа из ${APP_NAME} есть несколько простых действий:", "controlsText": "Управление", @@ -1602,6 +1603,7 @@ "Slovak": "Словацкий", "Spanish": "Испанский", "Swedish": "Шведский", + "Thai": "Тайский", "Turkish": "Турецкий", "Ukrainian": "Украинский", "Venetian": "Венецианский", @@ -1653,6 +1655,7 @@ "Account linking successful!": "Аккаунт успешно привязан!", "Account unlinking successful!": "Аккаунт успешно отвязан!", "Accounts are already linked.": "Аккаунты уже привязаны.", + "Ad view could not be verified.\nPlease be sure you are running an official and up-to-date version of the game.": "Просмотр рекламы не удалось проверить.\nПожалуйста, убедитесь, что вы используете официальную и актуальную версию игры.", "An error has occurred; (${ERROR})": "Произошла ошибка; (${ERROR})", "An error has occurred; please contact support. (${ERROR})": "произошла ошибка;Пожалуйста обратитесь в службу поддержки. (${ERROR})", "An error has occurred; please contact support@froemling.net.": "Произошла ошибка; пожалуйста, свяжитесь с support@froemling.net.", @@ -1939,6 +1942,7 @@ "getDriverText": "Скачать драйвер", "macInstructions2Text": "Для использования контроллеров по беспроводной связи, вам также\nпотребуется ресивер, который поставляется с \"беспроводным контроллером\nXbox 360 для Windows\". Один ресивер позволяет подключить до 4 контроллеров.\n\nВнимание: ресиверы сторонних производителей не будут работать с этим драйвером,\nубедитесь, что на вашем ресивере написано \"Microsoft\", а не \"XBOX 360\".\nMicrosoft больше не продает их отдельно, так что вам нужно будет найти\nресивер в комплекте с контроллером, либо искать на ebay.\n\nЕсли вы считаете это полезным, можете отправить денег разработчику\nдрайвера на его сайте.", "macInstructionsText": "Для использования контроллеров Xbox 360 необходимо\nустановить драйвер Mac, доступный по ссылке ниже.\nОн работает и с проводными и беспроводными контроллерами.", + "macInstructionsTextScale": 0.8, "ouyaInstructionsText": "Для использования проводных контроллеров Xbox 360 в BombSquad,\nпросто подключите их к USB-порту вашего устройства. Для нескольких\nконтроллеров можно использовать концентратор USB.\n\nДля использования беспроводных контроллеров вам понадобится беспроводной\nресивер который поставляется в наборе \"беспроводного геймпада Xbox 360\nдля Windows\" или продается отдельно. Каждый ресивер подключается\nк порту USB и позволяет подключать до 4 беспроводных контроллеров.", "titleText": "Использование контроллеров Xbox 360 в ${APP_NAME}:" }, diff --git a/dist/ba_data/data/languages/serbian.json b/dist/ba_data/data/languages/serbian.json index c714bef..a3d1a89 100644 --- a/dist/ba_data/data/languages/serbian.json +++ b/dist/ba_data/data/languages/serbian.json @@ -750,7 +750,7 @@ "partyStatusNotPublicText": "твоја зеједница није јавна", "pingText": "пинг", "portText": "Прикључак", - "privatePartyCloudDescriptionText": "Приватне заједнице које раде на серверима; није потребно подешавање рутера.", + "privatePartyCloudDescriptionText": "Приватне заједнице раде на серверима; није потребно подешавање рутера.", "privatePartyHostText": "Направи приватну заједницу", "privatePartyJoinText": "Придружи се заједници", "privateText": "Приватно", @@ -821,6 +821,7 @@ "bombInfoTextScale": 0.6, "canHelpText": "\"${APP_NAME}\" ти може помоћи.", "controllersInfoText": "Можеш да играш \"${APP_NAME}\" са пријатељима преко мреже, или \nможете играти на истом уређају ако имате довољно контролера.\n\"${APP_NAME}\" подржава доста њих; можете чак користити и \nтелефоне као контролере уз помоћ бесплатне \"${REMOTE_APP_NAME}\"\nапликације. Погледај Подешавања->Контролери за више информација.", + "controllersInfoTextRemoteOnly": "Можете да играте ${APP_NAME} са пријатељима преко мреже или са вама\nсви могу да се играју на истом уређају користећи телефоне као\nконтролери путем бесплатне апликације „${REMOTE_APP_NAME}“.", "controllersText": "Контролери", "controlsSubtitleText": "Твој пријатељ из \"${APP_NAME}\" игре има неколико основних акција:", "controlsText": "Контроле", @@ -860,7 +861,7 @@ "runInfoText": "- Трчање -\nДржи БИЛО КОЈЕ дугме да трчиш. Дугмад на повлачење или права раде боље ако их имате.\nТрчање ће ти помоћи да стигнеш на неко место брже али се теже окрећеш, зато пази на ивице.", "runInfoTextScale": 0.6, "someDaysExtraSpace": 0, - "someDaysText": "Неким данима се осећаш као да би желео да удараш нешто. Или да разносиш нешто у ваздух.", + "someDaysText": "Неким данима се осећаш као да би моаго јако да удараш у нешто. Или да разносиш нешто у ваздух.", "titleText": "\"${APP_NAME}\" помоћ", "toGetTheMostText": "Да би извукао највише из ове игре, требаће ти:", "welcomeText": "Добродошао у \"${APP_NAME}\"!" @@ -879,7 +880,7 @@ "buttonText": "дугме", "cantKickHostError": "Не можеш да избациш домаћина.", "chatBlockedText": "${NAME} не може да ћаска наредних ${TIME} секунди.", - "connectedToGameText": "${NAME} је ушао", + "connectedToGameText": "Придружио си серверу \"${NAME}\"", "connectedToPartyText": "Ушао си у ${NAME} заједницу!", "connectingToPartyText": "Повезивање...", "connectionFailedHostAlreadyInPartyText": "Конекција неуспела; домаћин је у другој заједници.", @@ -1527,6 +1528,7 @@ "Slovak": "Словачки", "Spanish": "Шпански", "Swedish": "Шведски", + "Thai": "Тхаи", "Turkish": "Турски", "Ukrainian": "Украјински", "Venetian": "Венецијански", @@ -1575,6 +1577,7 @@ "Account linking successful!": "Повезивање налога успешно!", "Account unlinking successful!": "Растављање налога успешно!", "Accounts are already linked.": "Налози су већ повезани.", + "Ad view could not be verified.\nPlease be sure you are running an official and up-to-date version of the game.": "Реклама није могла бити верификована.\nМолимо вас да будете сигурни да сте на официјалној и најновијој верзији игре.", "An error has occurred; (${ERROR})": "Дошло је до грешке; (${ERROR})", "An error has occurred; please contact support. (${ERROR})": "Дошло је до грешке; молимо вас обратите се подршци. (${ERROR})", "An error has occurred; please contact support@froemling.net.": "Дошло је до грешке; молимо контактирајте support@froemling.net.", @@ -1600,6 +1603,7 @@ "Max number of profiles reached.": "Максималан број профила достигнут.", "Maximum friend code rewards reached.": "Максималан број награда од пријатељских кодова достигнут.", "Message is too long.": "Порука је предугачка.", + "No servers are available. Please try again soon.": "Нема доступних сервера. Пробај опет касније.", "Profile \"${NAME}\" upgraded successfully.": "Профил \"${NAME}\" је успешно надограђен.", "Profile could not be upgraded.": "Профил не може бити надограђен.", "Purchase successful!": "Куповина успешна!", @@ -1609,6 +1613,7 @@ "Sorry, this code has already been used.": "Извињавамо се, овај код је већ употребљен.", "Sorry, this code has expired.": "Извињавамо се, код је истекао.", "Sorry, this code only works for new accounts.": "Извињавамо се, овај код ради само за нове налоге.", + "Still searching for nearby servers; please try again soon.": "Тражење сервера у близини још увек траје; покушај касније.", "Temporarily unavailable; please try again later.": "Привремено недоступно; молимо вас покушајте поново касније.", "The tournament ended before you finished.": "Турнир се завршио пре него што си завршио.", "This account cannot be unlinked for ${NUM} days.": "Овај налог не може бити растављен још ${NUM} дана.", diff --git a/dist/ba_data/data/languages/slovak.json b/dist/ba_data/data/languages/slovak.json index 9b61004..8e322ce 100644 --- a/dist/ba_data/data/languages/slovak.json +++ b/dist/ba_data/data/languages/slovak.json @@ -820,6 +820,7 @@ "bombInfoText": "- Bomba -\nSilnejšia ako údery, ale\nmôže spôsobiť vážne zranenie.\nPre najlepšie výsledky ju hoď\nna nepriateľa skôr ako vybúchne.", "canHelpText": "${APP_NAME} môže pomôcť.", "controllersInfoText": "Môžeš hrať ${APP_NAME} s kamarátmi cez internet, alebo môžete\nvšetci hrať na jednom zariadení pokiaľ máte dosť ovládačov.\n${APP_NAME} podporuje veľa z nich; môžeš pokojne použiť mobily\nako ovládače cez \"${REMOTE_APP_NAME}\" aplikáciu.\nPozri Nastavenia->Ovládače pre viac info.", + "controllersInfoTextRemoteOnly": "Bombsquad môžete hrať s priateľmi prostredníctvom siete alebo vy\nvšetky je možné hrať na rovnakom zariadení pomocou telefónov ako\novládače prostredníctvom bezplatnej aplikácie Bombsquad Vr", "controllersText": "Ovládače", "controlsSubtitleText": "Tvoj ${APP_NAME} charakter má pár základných zručností:", "controlsText": "Ovládanie", @@ -1517,6 +1518,7 @@ "Slovak": "Slovenčina", "Spanish": "Španielčina", "Swedish": "Švédčina", + "Thai": "Thajské", "Turkish": "Turečtina", "Ukrainian": "Ukrainčina", "Venetian": "Benátske", @@ -1565,6 +1567,7 @@ "Account linking successful!": "Prepojenie prebehlo úspešne!", "Account unlinking successful!": "Odpojenie prebehlo úspešne!", "Accounts are already linked.": "Účty už sú prepojené.", + "Ad view could not be verified.\nPlease be sure you are running an official and up-to-date version of the game.": "Zobrazenie reklamy nebolo možné overiť.\nUistite sa, že máte spustenú oficiálnu a aktuálnu verziu hry.", "An error has occurred; (${ERROR})": "Došlo k chybe; (${ERROR})", "An error has occurred; please contact support. (${ERROR})": "Došlo k chybe; prosím kontaktujte podporu. (${ERROR})", "An error has occurred; please contact support@froemling.net.": "Došlo k chybe; prosím kontaktujte support@froemling.net.", @@ -1590,6 +1593,7 @@ "Max number of profiles reached.": "Maximálny počet profilov dosiahnutý.", "Maximum friend code rewards reached.": "Maximálny počet odmien za nových hráčov dosiahnutý.", "Message is too long.": "Správa je príliš dlhá.", + "No servers are available. Please try again soon.": "K dispozícii nie sú žiadne servery. Skúste to znova čoskoro.", "Profile \"${NAME}\" upgraded successfully.": "Profil \"${NAME}\" bol úspešne vylepšený.", "Profile could not be upgraded.": "Profil nemožno vylepšiť.", "Purchase successful!": "Nákup prebehol úspešne!", @@ -1599,6 +1603,7 @@ "Sorry, this code has already been used.": "Prepáč, tento kód už bol použitý.", "Sorry, this code has expired.": "Prepáč, platnosť kódu vypršala.", "Sorry, this code only works for new accounts.": "Prepáč, tento kód funguje len pre nové účty.", + "Still searching for nearby servers; please try again soon.": "Still searching for nearby servers; please try again soon.", "Temporarily unavailable; please try again later.": "Dočasne nedostupné; prosím skús to znova neskôr.", "The tournament ended before you finished.": "Turnaj sa skončil predtým ako si ho dokončil.", "This account cannot be unlinked for ${NUM} days.": "Tento účet nemôže byť odpojený v podobu ${NUM} dní.", diff --git a/dist/ba_data/data/languages/spanish.json b/dist/ba_data/data/languages/spanish.json index 96b4b62..cee3401 100644 --- a/dist/ba_data/data/languages/spanish.json +++ b/dist/ba_data/data/languages/spanish.json @@ -862,6 +862,7 @@ "controllersInfoTextFantasia": "Un jugador puede usar el mando a distancia para jugar, pero\nse recomienda usar gamepads. También puedes usar dispositivos\niOS o Android como controles con la app \"BombSquad Remote\".\nRevisa la sección \"Controles\" en los Ajustes para más información.", "controllersInfoTextMac": "Uno o dos jugadores pueden usar el teclado, pero BombSquad es mejor\ncon controles. BombSquad puede usar controles USB, controles de PS3,\ncontroles de Xbox 360, controles Wii y dispositivos iOS/Android para\ncontrolar los personajes. Espero que tengas algunos de ellos a la mano. \nConsulta 'Controles' bajo 'Ajustes' para más información.", "controllersInfoTextOuya": "Puedes usar controles OUYA, controles de PS3, controles de Xbox\n360, y muchos de otros controles USB y Bluetooth con BombSquad.\nPuedes usar también dispositivos iOS/Android como controles con la aplicación \n'BombSquad Remote'. Consulta 'Controles' bajo 'Ajustes' para más información.", + "controllersInfoTextRemoteOnly": "Tu puedes jugar ${APP_NAME} con tus amigos en la red, o pueden\njugar todos en el mismo dispositivo usando los teléfonos\ncomo controles libres con la aplicación '${REMOTE_APP_NAME}'", "controllersText": "Controles", "controllersTextScale": 0.67, "controlsSubtitleText": "Tu personaje de ${APP_NAME} tiene algunas acciones básicas:", @@ -1076,7 +1077,7 @@ "modeClassicText": "Modo Clásico", "modeDemoText": "Modo De Demostración", "mostValuablePlayerText": "Jugador más valorado", - "mostViolatedPlayerText": "Jugador más Violado", + "mostViolatedPlayerText": "Jugador más agredido", "mostViolentPlayerText": "Jugador más Violento", "moveText": "Mover", "multiKillText": "¡¡¡${COUNT}-COMBO!!!", @@ -1617,6 +1618,8 @@ "Slovak": "Eslovaco", "Spanish": "Español", "Swedish": "Sueco", + "Tamil": "Tamil", + "Thai": "Tailandés", "Turkish": "Turco", "Ukrainian": "Ucraniano", "Venetian": "Veneciana", @@ -1668,6 +1671,7 @@ "Account linking successful!": "¡Enlace de Cuenta exitoso!", "Account unlinking successful!": "¡Desenlace de cuenta realizado!", "Accounts are already linked.": "Las cuentas ya se encuentran enlazadas.", + "Ad view could not be verified.\nPlease be sure you are running an official and up-to-date version of the game.": "No se pudo verificar la vista del anuncio. \nPor favor asegúrese de estar ejecutando una versión oficial y actualizada del juego.", "An error has occurred; (${ERROR})": "Se ha producido un error; (${ERROR})", "An error has occurred; please contact support. (${ERROR})": "Se ha producido un error; por favor contácte con soporte. (${ERROR})", "An error has occurred; please contact support@froemling.net.": "Ha ocurrido un error; contacta a support@froemling.net.", diff --git a/dist/ba_data/data/languages/tamil.json b/dist/ba_data/data/languages/tamil.json new file mode 100644 index 0000000..2b713ee --- /dev/null +++ b/dist/ba_data/data/languages/tamil.json @@ -0,0 +1,1854 @@ +{ + "accountSettingsWindow": { + "accountNameRules": "கணக்கு பெயர் தனித்துவமான எழுத்துக்களையோ எமோஜிக்களையோ கொண்டிருக்க முடியாது", + "accountsText": "கணக்குகள்", + "achievementProgressText": "சாதனைகள்: ${TOTAL} இல் ${COUNT}", + "campaignProgressText": "பிரச்சார முன்னேற்றம் [கடினமானது] : ${PROGRESS}", + "changeOncePerSeason": "ஒரு பருவத்திற்கு ஒரு முறை மட்டுமே இதை மாற்ற முடியும்.", + "changeOncePerSeasonError": "இதை மீண்டும் மாற்ற அடுத்த சீசன் வரை நீங்கள் காத்திருக்க வேண்டும் (${NUM} நாட்கள்)", + "customName": "தனிப்பயன் பெயர்", + "linkAccountsEnterCodeText": "குறியீட்டை உள்ளிடவும்", + "linkAccountsGenerateCodeText": "குறியீட்டை உருவாக்கவும்", + "linkAccountsInfoText": "வெவ்வேறு தளங்களில் முன்னேற்றத்தைப் பகிரலாம்", + "linkAccountsInstructionsNewText": "இரண்டு கணக்குகளை இணைக்க, முதலில் ஒரு குறியீட்டை உருவாக்கவும்\nஇரண்டாவது குறியீட்டை உள்ளிடவும். \n இலிருந்து தரவு\nஇரண்டாவது கணக்கு பின்னர் இருவருக்கும் இடையே பகிரப்படும். \n(முதல் கணக்கிலிருந்து தரவு இழக்கப்படும்)\n\nநீங்கள் ${COUNT} கணக்குகள் வரை இணைக்க முடியும்.\n\nமுக்கியம்: உங்களுக்கு சொந்தமான கணக்குகளை மட்டுமே இணைக்கவும்;\nநண்பர்களின் கணக்குகளுடன் நீங்கள் இணைத்தால் நீங்கள் ஒரே நேரத்தில் ஆன்லைனில் விளையாட முடியாது.", + "linkAccountsText": "கணக்குகளை இணைக்கவும்", + "linkedAccountsText": "இணைக்கப்பட்ட கணக்குகள்:", + "nameChangeConfirm": "உங்கள் கணக்கின் பெயரை ${NAME}க்கு மாற்ற", + "resetProgressConfirmNoAchievementsText": "இது உங்கள் கூட்டு முன்னேற்றங்கள் மற்றும் கருவி உயர்மதிப்பெண்கள் ஆகியவற்றை அழிக்கும் (ஆனால் உங்கள் சீட்டுகள் அல்ல).\nஇச்செயலை பின்வாங்க முடியாது.\nஉறுதியா?", + "resetProgressConfirmText": "இது உங்கள் கூட்டு முன்னேற்றங்கள், சாதனைகள், மற்றும் கருவி உயர்மதிப்பெண்கள் ஆகியவற்றை அழிக்கும் \n(ஆனால் உங்கள் சீட்டுகள் அல்ல).\nஇச்செயலை பின்வாங்க முடியாது.\nஉறுதியா?", + "resetProgressText": "முன்னேற்றங்களை அழி", + "setAccountName": "கணக்கு பெயர் வைக்க", + "setAccountNameDesc": "உங்கள் கணக்குக்கு காட்ட வேண்டிய பெயரை தேர்வு செய்யவும். \nஉங்கள் இணைக்கப்பட்ட கணக்குகளிலிருந்து ஒரு பெயரை தேர்வு செய்யலாம் \nஅல்லது புதிய பெயரை உருவாக்கலாம்.", + "signInInfoText": "சீட்டுகள் சேர்க்க, இணையத்தில் போட்டியிட,\nமுன்னேற்றங்களை பகிர, புகுபதிவு செய்யவும்.", + "signInText": "புகுபதிகை", + "signInWithDeviceInfoText": "(இந்த சாதனத்திலிருந்து ஒரு தானியங்கி கணக்கு மட்டுமே கிடைக்கும்)", + "signInWithDeviceText": "சாதனக் கணக்கில் உள்நுழைக", + "signInWithGameCircleText": "Game Circle மூலம் உள்நுழைக", + "signInWithGooglePlayText": "Google Play உடன் உள்நுழைக", + "signInWithTestAccountInfoText": "(மரபு கணக்கு வகை; முன்னோக்கி செல்லும் சாதனக் கணக்குகளைப் பயன்படுத்தவும்)", + "signInWithTestAccountText": "சோதனைக் கணக்கில் உள்நுழைக", + "signOutText": "வெளியேறு", + "signingInText": "புகுபதிகை நடைபெறுகிறது...", + "signingOutText": "விடுபதிகை நடைபெறுகிறது...", + "ticketsText": "டிக்கெட்: ${COUNT}", + "titleText": "கணக்கு", + "unlinkAccountsInstructionsText": "இணைப்பை நீக்குவதற்கான கணக்கைத் தேர்ந்தெடுக்கவும்", + "unlinkAccountsText": "கணக்கை நீக்க", + "viaAccount": "${NAME} கணக்கின் வழியாக", + "youAreSignedInAsText": "நீங்கள் உள்நுழைந்துள்ளீர்கள்" + }, + "achievementChallengesText": "சாதனை சவால்கள்", + "achievementText": "சாதனை", + "achievements": { + "Boom Goes the Dynamite": { + "description": "டிஎன்டி மூலம் 3 கெட்டவர்களைக் கொல்லுங்கள்", + "descriptionComplete": "TNT உடன் 3 கெட்டவர்களைக் கொன்றது", + "descriptionFull": "TNT உடன் 3 கெட்டவர்களை ${LEVEL} இல் கொல்லுங்கள்", + "descriptionFullComplete": "${LEVEL} இல் TNT உடன் 3 கெட்டவர்களைக் கொன்றது", + "name": "பூம் டைனமைட்டுக்கு செல்கிறது" + }, + "Boxer": { + "description": "எந்த குண்டுகளையும் பயன்படுத்தாமல் வெற்றி பெறுங்கள்", + "descriptionComplete": "எந்த குண்டுகளையும் பயன்படுத்தாமல் வெற்றி பெற்றது", + "descriptionFull": "எந்த குண்டுகளையும் பயன்படுத்தாமல் ${LEVEL} ஐ முடிக்கவும்", + "descriptionFullComplete": "எந்த வெடிகுண்டையும் பயன்படுத்தாமல் ${LEVEL} ஐ நிறைவு செய்தது", + "name": "பாக்சர்" + }, + "Dual Wielding": { + "descriptionFull": "2 கட்டுப்படுத்திகளை இணைக்கவும் (வன்பொருள் அல்லது செயலி)", + "descriptionFullComplete": "இணைக்கப்பட்ட 2 கட்டுப்படுத்திகள் (வன்பொருள் அல்லது செயலி)", + "name": "இரட்டை வீல்டிங்" + }, + "Flawless Victory": { + "description": "அடிபடாமல் வெற்றி பெரு", + "descriptionComplete": "அடிபடாமல் வென்றது", + "descriptionFull": "அடிபடாமல் ${LEVEL} ஐ வெற்றி பெறு", + "descriptionFullComplete": "அடிபடாமல் ${LEVEL} வென்றது", + "name": "குறைபாடற்ற வெற்றி" + }, + "Free Loader": { + "descriptionFull": "2+ பிளேயர்களுடன் அனைவருக்கும் இலவச விளையாட்டைத் தொடங்கவும்", + "descriptionFullComplete": "2+ பிளேயர்களுடன் அனைவருக்கும் இலவச விளையாட்டைத் தொடங்கியது", + "name": "இலவச ஏற்றி" + }, + "Gold Miner": { + "description": "நிலக் கண்ணிவெடிகளால் 6 கெட்டவர்களைக் கொல்லுங்கள்", + "descriptionComplete": "கண்ணிவெடிகளால் 6 கெட்டவர்களைக் கொன்றது", + "descriptionFull": "${LEVEL} இல் கண்ணிவெடிகளுடன் 6 கெட்டவர்களைக் கொல்லுங்கள்", + "descriptionFullComplete": "${LEVEL} இல் கண்ணிவெடிகளுடன் 6 கெட்டவர்களைக் கொன்றது", + "name": "தங்க சுரங்கத் தொழிலாளி" + }, + "Got the Moves": { + "description": "குத்து அல்லது குண்டுகளைப் பயன்படுத்தாமல் வெற்றி பெறுங்கள்", + "descriptionComplete": "குத்துக்கள் அல்லது குண்டுகளைப் பயன்படுத்தாமல் வென்றது", + "descriptionFull": "குத்துக்கள் அல்லது குண்டுகள் இல்லாமல் ${LEVEL} ஐ வெற்றி பெரு", + "descriptionFullComplete": "குத்துக்கள் அல்லது குண்டுகள் இல்லாமல் ${LEVEL} வென்றது", + "name": "நகர்வுகள் கிடைத்தன" + }, + "In Control": { + "descriptionFull": "ஒரு கட்டுப்படுத்தியை இணைக்கவும் (வன்பொருள் அல்லது செயலி)", + "descriptionFullComplete": "ஒரு கட்டுப்படுத்தி இணைக்கப்பட்டுள்ளது (வன்பொருள் அல்லது செயலி)", + "name": "கட்டுப்பாட்டில் உள்ளது" + }, + "Last Stand God": { + "description": "1000 புள்ளிகளைப் பெறுங்கள்", + "descriptionComplete": "1000 புள்ளிகள் பெற்றுள்ளார்", + "descriptionFull": "${LEVEL} இல் 1000 புள்ளிகளைப் பெறுங்கள்", + "descriptionFullComplete": "${LEVEL} இல் 1000 புள்ளிகளைப் பெற்றுள்ளது", + "name": "${LEVEL} கடவுள்" + }, + "Last Stand Master": { + "description": "250 புள்ளிகளைப் பெறுங்கள்", + "descriptionComplete": "250 புள்ளிகள் பெற்றுள்ளார்", + "descriptionFull": "${LEVEL} இல் 250 புள்ளிகளைப் பெறுங்கள்", + "descriptionFullComplete": "${LEVEL} இல் 250 புள்ளிகளைப் பெற்றுள்ளார்", + "name": "${LEVEL} குரு" + }, + "Last Stand Wizard": { + "description": "500 புள்ளிகளைப் பெறுங்கள்", + "descriptionComplete": "500 புள்ளிகள் பெற்றுள்ளது", + "descriptionFull": "${LEVEL} இல் 500 புள்ளிகளைப் பெறுங்கள்", + "descriptionFullComplete": "${LEVEL} இல் 500 புள்ளிகளைப் பெற்றுள்ளது", + "name": "${LEVEL} மந்திரவாதி" + }, + "Mine Games": { + "description": "நிலக் கண்ணிவெடிகளால் 3 கெட்டவர்களைக் கொல்லுங்கள்", + "descriptionComplete": "கண்ணிவெடிகளால் 3 கெட்டவர்களைக் கொன்றது", + "descriptionFull": "${LEVEL} இல் நிலக் கண்ணிவெடிகளுடன் 3 கெட்டவர்களைக் கொல்லுங்கள்", + "descriptionFullComplete": "${LEVEL} இல் கண்ணிவெடிகளுடன் 3 கெட்டவர்களைக் கொன்றது", + "name": "நில-சுரங்க விளையாட்டுகள்" + }, + "Off You Go Then": { + "description": "3 கெட்டவர்களை இடத்திலிருந்து தூக்கி எறியுங்கள்", + "descriptionComplete": "அந்த இடத்திலிருந்து 3 கெட்டவர்கள் வீசப்பட்டனர்", + "descriptionFull": "${LEVEL} இல் 3 கெட்டவர்களை இடத்திலிருந்து தூக்கி எறியுங்கள்", + "descriptionFullComplete": "${LEVEL} இல் அந்த இடத்திலிருந்து 3 கெட்டவர்கள் வீசப்பட்டனர்", + "name": "புறப்படுங்கள் பிறகு" + }, + "Onslaught God": { + "description": "5000 புள்ளிகளைப் பெறுங்கள்", + "descriptionComplete": "5000 புள்ளிகள் பெற்றுள்ளது", + "descriptionFull": "${LEVEL} இல் 5000 புள்ளிகளைப் பெறுங்கள்", + "descriptionFullComplete": "${LEVEL} இல் 5000 புள்ளிகளைப் பெற்றுள்ளது", + "name": "${LEVEL} கடவுள்" + }, + "Onslaught Master": { + "description": "500 புள்ளிகளைப் பெறுங்கள்", + "descriptionComplete": "500 புள்ளிகள் பெற்றுள்ளது", + "descriptionFull": "${LEVEL} இல் 500 புள்ளிகளைப் பெறுங்கள்", + "descriptionFullComplete": "${LEVEL} இல் 500 புள்ளிகளைப் பெற்றுள்ளது", + "name": "${LEVEL} குரு" + }, + "Onslaught Training Victory": { + "description": "அனைத்து அலைகளையும் தோற்கடிக்கவும்", + "descriptionComplete": "அனைத்து அலைகளையும் தோற்கடித்தது", + "descriptionFull": "அனைத்து அலைகளையும் ${LEVEL} இல் தோற்கடிக்கவும்", + "descriptionFullComplete": "${LEVEL} இல் அனைத்து அலைகளையும் தோற்கடித்தது", + "name": "${LEVEL} வெற்றி" + }, + "Onslaught Wizard": { + "description": "1000 புள்ளிகளைப் பெறுங்கள்", + "descriptionComplete": "1000 புள்ளிகளைப் பெற்றுள்ளார்", + "descriptionFull": "${LEVEL} இல் 1000 புள்ளிகளைப் பெறுங்கள்", + "descriptionFullComplete": "${LEVEL} இல் 1000 புள்ளிகளைப் பெற்றுள்ளது", + "name": "${LEVEL} மந்திரவாதி" + }, + "Precision Bombing": { + "description": "எந்த சக்தியும் இல்லாமல் வெற்றி பெரு", + "descriptionComplete": "எந்த சக்தியும் இல்லாமல் வெற்றி பெற்றது", + "descriptionFull": "எந்த பவர் அப்களையும் பயன்படுத்தாமல் ${LEVEL} ஐ வெல்லுங்கள்", + "descriptionFullComplete": "எந்த பவர் அப்களையும் பயன்படுத்தாமல் ${LEVEL} வெற்றி பெற்றது", + "name": "துல்லியமான குண்டுவீச்சு" + }, + "Pro Boxer": { + "description": "எந்த குண்டுகளையும் பயன்படுத்தாமல் வெற்றி பெரு", + "descriptionComplete": "எந்த குண்டுகளையும் பயன்படுத்தாமல் வெற்றி பெற்றார்", + "descriptionFull": "எந்த குண்டுகளையும் பயன்படுத்தாமல் ${LEVEL} ஐ முடிக்கவும்", + "descriptionFullComplete": "எந்த வெடிகுண்டையும் பயன்படுத்தாமல் ${LEVEL} ஐ முடித்தார்", + "name": "ப்ரோ பாக்ஸர்" + }, + "Pro Football Shutout": { + "description": "கெட்டவர்கள் மதிப்பெண் பெறாமல் வெற்றி பெரு", + "descriptionComplete": "கெட்டவர்களை மதிப்பெண் பெற விடாமல் வெற்றி பெட்ரார்", + "descriptionFull": "கெட்டவர்களை மதிப்பெண் பெற விடாமல் ${LEVEL} வெற்றி பெரு", + "descriptionFullComplete": "கெட்டவர்களை மதிப்பெண் பெற விடாமல் ${LEVEL} வெற்றி", + "name": "${LEVEL} பணிநிறுத்தம்" + }, + "Pro Football Victory": { + "description": "விளையாட்டை வெற்றி பெரு", + "descriptionComplete": "விளையாட்டை வென்றது", + "descriptionFull": "${LEVEL} இல் விளையாட்டை வெல்லுங்கள்", + "descriptionFullComplete": "${LEVEL} இல் விளையாட்டை வென்றது", + "name": "${LEVEL} வெற்றி" + }, + "Pro Onslaught Victory": { + "description": "அனைத்து அலைகளையும் தோற்கடிக்கவும்", + "descriptionComplete": "அனைத்து அலைகளையும் தோற்கடித்தது", + "descriptionFull": "${LEVEL} இன் அனைத்து அலைகளையும் தோற்கடிக்கவும்", + "descriptionFullComplete": "${LEVEL} இன் அனைத்து அலைகளையும் தோற்கடித்தது", + "name": "${LEVEL} வெற்றி" + }, + "Pro Runaround Victory": { + "description": "அனைத்து அலைகளையும் முடிக்கவும்", + "descriptionComplete": "அனைத்து அலைகளையும் முடிகப்பட்டது", + "descriptionFull": "அனைத்து அலைகளையும் ${LEVEL} இல் முடிக்கவும்", + "descriptionFullComplete": "அனைத்து அலைகளையும் ${LEVEL} இல் முடிக பட்டது", + "name": "${LEVEL} வெற்றி" + }, + "Rookie Football Shutout": { + "description": "கெட்டவர்களை மதிப்பெண் பெற விடாமல் வெற்றி பெரு", + "descriptionComplete": "கெட்டவர்களை மதிப்பெண் பெற விடாமல் வெற்றி பெட்ரது", + "descriptionFull": "கெட்டவர்களை மதிப்பெண் பெற விடாமல் ${LEVEL} வெல்லுங்கள்", + "descriptionFullComplete": "கெட்டவர்களை மதிப்பெண் பெற விடாமல் ${LEVEL} வென்றது", + "name": "${LEVEL} பணிநிறுத்தம்" + }, + "Rookie Football Victory": { + "description": "விளையாட்டை வெற்றி பெரு", + "descriptionComplete": "விளையாட்டை வென்றது", + "descriptionFull": "${LEVEL} இல் விளையாட்டை வெல்லுங்கள்", + "descriptionFullComplete": "${LEVEL} இல் விளையாட்டை வென்றது", + "name": "${LEVEL} வெற்றி" + }, + "Rookie Onslaught Victory": { + "description": "அனைத்து அலைகளையும் தோற்கடிக்கவும்", + "descriptionComplete": "அனைத்து அலைகளையும் தோற்கடித்தது", + "descriptionFull": "அனைத்து அலைகளையும் ${LEVEL} இல் தோற்கடிக்கவும்", + "descriptionFullComplete": "அனைத்து அலைகளையும் ${LEVEL} இல் தோற்கடித்தது", + "name": "${LEVEL} வெற்றி" + }, + "Runaround God": { + "description": "2000 புள்ளிகளைப் பெறுங்கள்", + "descriptionComplete": "2000 புள்ளிகளைப் பெற்றார்", + "descriptionFull": "${LEVEL} இல் 2000 புள்ளிகளைப் பெறுங்கள்", + "descriptionFullComplete": "${LEVEL} இல் 2000 புள்ளிகளைப் பெற்றார்", + "name": "${LEVEL} கடவுள்" + }, + "Runaround Master": { + "description": "500 புள்ளிகளைப் பெறுங்கள்", + "descriptionComplete": "500 புள்ளிகளைப் பெற்றார்", + "descriptionFull": "${LEVEL} இல் 500 புள்ளிகளைப் பெறுங்கள்", + "descriptionFullComplete": "${LEVEL} இல் 500 புள்ளிகளைப் பெற்றார்", + "name": "${LEVEL} தலைவன்" + }, + "Runaround Wizard": { + "description": "1000 புள்ளிகளைப் பெறுங்கள்", + "descriptionComplete": "1000 புள்ளிகளைப் பெற்றார்", + "descriptionFull": "${LEVEL} இல் 1000 புள்ளிகளைப் பெறுங்கள்", + "descriptionFullComplete": "${LEVEL} இல் 1000 புள்ளிகளைப் பெற்றார்", + "name": "${LEVEL} மந்திரவாதி" + }, + "Sharing is Caring": { + "descriptionFull": "விளையாட்டை வெற்றிகரமாக நண்பருடன் பகிர்ந்து கொள்ளுங்கள்", + "descriptionFullComplete": "வெற்றிகரமாக ஒரு நண்பருடன் விளையாட்டை பகிர்ந்து கொண்டார்", + "name": "பகிர்தலே அக்கறை காட்டுதல்" + }, + "Stayin' Alive": { + "description": "இறக்காமல் வெற்றி பெரு", + "descriptionComplete": "இறக்காமல் வென்றது", + "descriptionFull": "இறக்காமல் ${LEVEL} வெல்லுங்கள்", + "descriptionFullComplete": "இறக்காமல் ${LEVEL} வென்றது", + "name": "உயிருடன் இருங்கள்" + }, + "Super Mega Punch": { + "description": "ஒரு குத்து மூலம் 100% சேதத்தை ஏற்படுத்தும்", + "descriptionComplete": "ஒரு குத்து மூலம் 100% சேதத்தை ஏற்படுத்தியது", + "descriptionFull": "${LEVEL} இல் ஒரு குத்து மூலம் 100% சேதத்தை ஏற்படுத்தவும்", + "descriptionFullComplete": "${LEVEL} இல் ஒரு குத்து மூலம் 100% சேதத்தை ஏற்படுத்தியது", + "name": "சூப்பர் மெகா குத்து" + }, + "Super Punch": { + "description": "ஒரு குத்து மூலம் 50% சேதத்தை ஏற்படுத்தவும்", + "descriptionComplete": "ஒரு குத்து மூலம் 50% சேதத்தை ஏற்படுத்தியது", + "descriptionFull": "${LEVEL} இல் ஒரு குத்து மூலம் 50% சேதத்தை ஏற்படுத்தவும்", + "descriptionFullComplete": "${LEVEL} இல் ஒரு குத்து மூலம் 50% சேதத்தை ஏற்படுத்தியது", + "name": "சூப்பர் குத்து" + }, + "TNT Terror": { + "description": "TNT மூலம் 6 கெட்டவர்களைக் கொல்லுங்கள்", + "descriptionComplete": "TNT ஆல் 6 கெட்டவர்களைக் கொன்றது", + "descriptionFull": "TNT உடன் 6 கெட்டவர்களை ${LEVEL} இல் கொல்லுங்கள்", + "descriptionFullComplete": "${LEVEL} இல் TNT உடன் 6 கெட்டவர்களைக் கொன்றது", + "name": "TNT பயங்கரவாதம்" + }, + "Team Player": { + "descriptionFull": "4+ வீரர்களுடன் குழு விளையாட்டைத் தொடங்குங்கள்", + "descriptionFullComplete": "4+ வீரர்களுடன் ஒரு குழு விளையாட்டைத் தொடங்கினார்", + "name": "அணி வீரர்" + }, + "The Great Wall": { + "description": "ஒவ்வொரு கெட்டவரையும் நிறுத்துங்கள்", + "descriptionComplete": "ஒவ்வொரு கெட்டவரையும் நிறுத்தியது", + "descriptionFull": "ஒவ்வொரு கெட்டவரையும் ${LEVEL} இல் நிறுத்துங்கள்", + "descriptionFullComplete": "ஒவ்வொரு கெட்டவரையும் ${LEVEL} இல் நிறுத்தியது", + "name": "பெருஞ்சுவர்" + }, + "The Wall": { + "description": "ஒவ்வொரு கெட்டவரையும் நிறுத்துங்கள்", + "descriptionComplete": "ஒவ்வொரு கெட்டவரையும் நிறுத்தியது", + "descriptionFull": "ஒவ்வொரு கெட்டவரையும் ${LEVEL} இல் நிறுத்துங்கள்", + "descriptionFullComplete": "ஒவ்வொரு கெட்டவரையும் ${LEVEL} இல் நிறுத்தியது", + "name": "சுவர்" + }, + "Uber Football Shutout": { + "description": "கெட்டவர்களை மதிப்பெண் பெற விடாமல் வெற்றி பெறுங்கள்", + "descriptionComplete": "கெட்டவர்களை மதிப்பெண் பெற விடாமல் வென்றது", + "descriptionFull": "கெட்டவர்களை மதிப்பெண் பெற விடாமல் ${LEVEL} வெல்லுங்கள்", + "descriptionFullComplete": "கெட்டவர்களை மதிப்பெண் பெற விடாமல் ${LEVEL} வென்றது", + "name": "${LEVEL} பணிநிறுத்தம்" + }, + "Uber Football Victory": { + "description": "விளையாட்டை வெற்றி பெரு", + "descriptionComplete": "விளையாட்டை வென்றது", + "descriptionFull": "${LEVEL} இல் விளையாட்டை வெல்லுங்கள்", + "descriptionFullComplete": "${LEVEL} இல் விளையாட்டை வென்றது", + "name": "${LEVEL} வெற்றி" + }, + "Uber Onslaught Victory": { + "description": "அனைத்து அலைகளையும் தோற்கடிக்கவும்", + "descriptionComplete": "அனைத்து அலைகளையும் தோற்கடித்தது", + "descriptionFull": "அனைத்து அலைகளையும் ${LEVEL} இல் தோற்கடிக்கவும்", + "descriptionFullComplete": "${LEVEL} இல் அனைத்து அலைகளையும் தோற்கடித்தது", + "name": "${LEVEL} வெற்றி" + }, + "Uber Runaround Victory": { + "description": "அனைத்து அலைகளையும் முடிக்கவும்", + "descriptionComplete": "அனைத்து அலைகளையும் முடித்தது", + "descriptionFull": "அனைத்து அலைகளையும் ${LEVEL} இல் முடிக்கவும்", + "descriptionFullComplete": "அனைத்து அலைகளையும் ${LEVEL} இல் முடித்தது", + "name": "${LEVEL} வெற்றி" + } + }, + "achievementsRemainingText": "மீதமுள்ள சாதனைகள்:", + "achievementsText": "சாதனைகள்", + "achievementsUnavailableForOldSeasonsText": "மன்னிக்கவும், சாதனை விவரங்கள் பழைய பருவங்களுக்கு கிடைக்கவில்லை.", + "addGameWindow": { + "getMoreGamesText": "மேலும் விளையாட்டுகளைப் பெறுங்கள்...", + "titleText": "விளையாட்டைச் சேர்" + }, + "allowText": "அனுமதி", + "alreadySignedInText": "உங்கள் கணக்கு மற்றொரு சாதனத்திலிருந்து உள்நுழைந்துள்ளது;\nதயவுசெய்து கணக்குகளை மாற்றவும் அல்லது விளையாட்டை மூடவும்\nபிற சாதனங்கள் மற்றும் மீண்டும் முயற்சிக்கவும்.", + "apiVersionErrorText": "${NAME} தொகுதியை ஏற்ற முடியவில்லை; இது api-version ${VERSION_USED} ஐ குறிவைக்கிறது; எங்களுக்கு ${VERSION_REQUIRED} தேவைப்படுகிறது.", + "audioSettingsWindow": { + "headRelativeVRAudioInfoText": "(ஹெட்ஃபோன்கள் செருகப்படும்போது மட்டுமே \"ஆட்டோ\" இதை இயக்குகிறது)", + "headRelativeVRAudioText": "தலை-உறவினர் VR ஆடியோ", + "musicVolumeText": "இசை ஒலி அளவு", + "soundVolumeText": "ஒலி அளவு", + "soundtrackButtonText": "ஒலிப்பதிவுகள்", + "soundtrackDescriptionText": "(விளையாட்டுகளின் போது விளையாட உங்கள் சொந்த இசையை ஒதுக்குங்கள்)", + "titleText": "ஒலி அமைவு" + }, + "autoText": "Auto", + "backText": "பின்னால்", + "banThisPlayerText": "இந்த பிளேயரை தடை செய்யவும்", + "bestOfFinalText": "சிறந்த-${COUNT} இறுதி", + "bestOfSeriesText": "${COUNT} தொடரின் சிறந்தவை:", + "bestOfUseFirstToInstead": 0, + "bestRankText": "உங்கள் சிறந்த #${RANK}", + "bestRatingText": "உங்கள் சிறந்த மதிப்பீடு ${RATING}", + "bombBoldText": "வெடி குண்டு", + "bombText": "வெடிகுண்டு", + "boostText": "ஊக்குவிக்கவும்", + "bsRemoteConfigureInAppText": "${REMOTE_APP_NAME} பயன்பாட்டில் உள்ளமைக்கப்பட்டுள்ளது.", + "buttonText": "பொத்தானை", + "canWeDebugText": "BombSquad தானாகப் புகாரளிக்க விரும்புகிறீர்களா?\nடெவலப்பருக்கு பிழைகள், செயலிழப்புகள் மற்றும் அடிப்படை பயன்பாட்டு தகவல்?\n\nஇந்தத் தரவில் தனிப்பட்ட தகவல் இல்லை மற்றும் உதவுகிறது\nவிளையாட்டை சீராக மற்றும் பிழையில்லாமல் வைத்துக்கொள்ளுங்கள்.", + "cancelText": "ரத்து", + "cantConfigureDeviceText": "மன்னிக்கவும்,${DEVICE} கட்டமைக்கப்படவில்லை.", + "challengeEndedText": "இந்த சவால் முடிந்தது.", + "chatMuteText": "அரட்டை முடக்கு", + "chatMutedText": "அரட்டை முடக்கப்பட்டது", + "chatUnMuteText": "அரட்டையை இயக்கு", + "choosingPlayerText": "<பிளேயரைத் தேர்ந்தெடுப்பது>", + "completeThisLevelToProceedText": "தொடர நீங்கள்\n இந்த நிலையை முடிக்க வேண்டும்!", + "completionBonusText": "நிறைவு போனஸ்", + "configControllersWindow": { + "configureControllersText": "கட்டுப்பாட்டாளர்களை உள்ளமைக்கவும்", + "configureKeyboard2Text": "விசைப்பலகை P2 ஐ உள்ளமைக்கவும்", + "configureKeyboardText": "விசைப்பலகையை உள்ளமைக்கவும்", + "configureMobileText": "கட்டுப்பாட்டாளர்களாக மொபைல் சாதனங்கள்", + "configureTouchText": "தொடுதிரையை உள்ளமைக்கவும்", + "ps3Text": "PS3 கட்டுப்பாட்டாளர்கள்", + "titleText": "கட்டுப்பாட்டாளர்கள்", + "wiimotesText": "விமோட்ஸ்", + "xbox360Text": "Xbox 360 கட்டுப்பாட்டாளர்கள்" + }, + "configGamepadSelectWindow": { + "androidNoteText": "குறிப்பு: கட்டுப்படுத்தி ஆதரவு சாதனம் மற்றும் ஆண்ட்ராய்டு பதிப்பைப் பொறுத்து மாறுபடும்.", + "pressAnyButtonText": "கட்டுப்படுத்தியின் எந்த பொத்தானையும் அழுத்தவும்\nநீங்கள் கட்டமைக்க விரும்புகிறீர்கள்...", + "titleText": "கட்டுப்பாட்டாளர்களை உள்ளமைக்கவும்" + }, + "configGamepadWindow": { + "advancedText": "உயர்தரமான", + "advancedTitleText": "மேம்பட்ட கட்டுப்படுத்தி அமைப்பு", + "analogStickDeadZoneDescriptionText": "(நீங்கள் குச்சியை வெளியிடும் போது உங்கள் எழுத்து 'திசைதிருப்பினால்' இதை இயக்கவும்)", + "analogStickDeadZoneText": "அனலாக் ஸ்டிக் டெட் மண்டலம்", + "appliesToAllText": "(இந்த வகை அனைத்து கட்டுப்படுத்திகளுக்கும் பொருந்தும்)", + "autoRecalibrateDescriptionText": "(உங்கள் எழுத்து முழு வேகத்தில் நகரவில்லை என்றால் இதை இயக்கவும்)", + "autoRecalibrateText": "அனலாக் குச்சியை தானாக மறுபரிசீலனை செய்யுங்கள்", + "axisText": "அச்சு", + "clearText": "தெளிந்த", + "dpadText": "dpad", + "extraStartButtonText": "கூடுதல் தொடக்க பட்டன்", + "ifNothingHappensTryAnalogText": "எதுவும் நடக்கவில்லை என்றால், அதற்கு பதிலாக அனலாக் ஸ்டிக்கிற்கு ஒதுக்க முயற்சிக்கவும்.", + "ifNothingHappensTryDpadText": "எதுவும் நடக்கவில்லை என்றால், அதற்கு பதிலாக D-pad டிற்கு ஒதுக்க முயற்சிக்கவும்.", + "ignoreCompletelyDescriptionText": "(இந்த கட்டுப்படுத்தி விளையாட்டு அல்லது மெனுக்களை பாதிக்காமல் தடுக்கவும்)", + "ignoreCompletelyText": "முற்றிலும் புறக்கணிக்கவும்", + "ignoredButton1Text": "புறக்கணிக்கப்பட்ட பட்டன் 1", + "ignoredButton2Text": "புறக்கணிக்கப்பட்ட பட்டன் 2", + "ignoredButton3Text": "புறக்கணிக்கப்பட்ட பட்டன் 3", + "ignoredButton4Text": "புறக்கணிக்கப்பட்ட பட்டன் 4", + "ignoredButtonDescriptionText": "('முகப்பு' அல்லது 'ஒத்திசைவு' பொத்தான்கள் UI ஐ பாதிக்காமல் தடுக்க இதைப் பயன்படுத்தவும்)", + "pressAnyAnalogTriggerText": "எந்த அனலாக் தூண்டுதலையும் அழுத்தவும்...", + "pressAnyButtonOrDpadText": "எந்த பட்டன் அல்லது dpad ஐ அழுத்தவும்...", + "pressAnyButtonText": "எந்த பட்டனையும் அழுத்தவும்...", + "pressLeftRightText": "இடது அல்லது வலது அழுத்தவும்...", + "pressUpDownText": "மேலே அல்லது கீழ் அழுத்தவும்...", + "runButton1Text": "ரன் பட்டன் 1", + "runButton2Text": "ரன் பட்டன் 2", + "runTrigger1Text": "இயக்க தூண்டுதல் 1", + "runTrigger2Text": "இயக்க தூண்டுதல் 2", + "runTriggerDescriptionText": "(அனலாக் தூண்டுதல்கள் உங்களை மாறி வேகத்தில் இயக்க அனுமதிக்கின்றன)", + "secondHalfText": "இரண்டாவது பாதியை உள்ளமைக்க இதைப் பயன்படுத்தவும்\n2-கட்டுப்படுத்திகள்-இன் -1 சாதனத்தின்\nஒற்றை கட்டுப்பாட்டாளராகக் காட்டுகிறது.", + "secondaryEnableText": "இயக்கு", + "secondaryText": "இரண்டாம் நிலை கட்டுப்பாட்டாளர்", + "startButtonActivatesDefaultDescriptionText": "(உங்கள் தொடக்க பொத்தானை 'மெனு' பொத்தானை விட அதிகமாக இருந்தால் இதை அணைக்கவும்)", + "startButtonActivatesDefaultText": "தொடக்க பட்டன் இயல்புநிலை விட்ஜெட்டை செயல்படுத்துகிறது", + "titleText": "கட்டுப்படுத்தி அமைப்பு", + "twoInOneSetupText": "2-in-1 கட்டுப்படுத்தி அமைப்பு", + "uiOnlyDescriptionText": "(இந்த கட்டுப்படுத்தி உண்மையில் ஒரு விளையாட்டில் சேர்வதைத் தடுக்கவும்)", + "uiOnlyText": "மெனு உபயோகத்திற்கு வரம்பு", + "unassignedButtonsRunText": "ஒதுக்கப்படாத அனைத்து பட்டன் இயங்குகின்றன", + "unsetText": "<அமைக்கப்படவில்லை>", + "vrReorientButtonText": "VR மறுசீரமைப்பு பட்டன்" + }, + "configKeyboardWindow": { + "configuringText": "${DEVICE} ஐ உள்ளமைக்கிறது", + "keyboard2NoteText": "குறிப்பு: பெரும்பாலான விசைப்பலகைகள் ஒரு சில விசை அழுத்தங்களை மட்டுமே பதிவு செய்ய முடியும்\nஒரு முறை, அதனால் இரண்டாவது விசைப்பலகை பிளேயர் இருந்தால் நன்றாக வேலை செய்யலாம்\nஅவர்கள் பயன்படுத்த தனி விசைப்பலகை இணைக்கப்பட்டிருந்தால்.\nநீங்கள் இன்னும் தனிப்பட்ட விசைகளை ஒதுக்க வேண்டும் என்பதை நினைவில் கொள்க\nஅந்த வழக்கில் கூட இரண்டு வீரர்கள்." + }, + "configTouchscreenWindow": { + "actionControlScaleText": "நடவடிக்கை கட்டுப்பாட்டு அளவு", + "actionsText": "செயல்கள்", + "buttonsText": "பட்டன்கள்", + "dragControlsText": "<கட்டுப்பாடுகளை இடமாற்றம் செய்ய இழுக்கவும்>", + "joystickText": "ஜாய்ஸ்டிக்", + "movementControlScaleText": "இயக்கக் கட்டுப்பாட்டு அளவு", + "movementText": "இயக்கம்", + "resetText": "மீட்டமை", + "swipeControlsHiddenText": "ஸ்வைப் ஐகான்களை மறைக்கவும்", + "swipeInfoText": "'ஸ்வைப்' பாணி கட்டுப்பாடுகள் கொஞ்சம் பழகிவிடும் ஆனால்\nகட்டுப்பாடுகளை பார்க்காமல் விளையாடுவதை எளிதாக்குங்கள்.", + "swipeText": "ஸ்வைப்", + "titleText": "தொடுதிரையை உள்ளமைக்கவும்" + }, + "configureItNowText": "இப்போது அதை உள்ளமைக்கவா?", + "configureText": "உள்ளமை", + "connectMobileDevicesWindow": { + "amazonText": "அமேசான் ஆப்ஸ்டோர்", + "appStoreText": "ஆப் ஸ்டோர்", + "bestResultsScale": 0.65, + "bestResultsText": "சிறந்த முடிவுகளுக்கு உங்களுக்கு பின்னடைவு இல்லாத வைஃபை நெட்வொர்க் தேவை. உன்னால் முடியும்\nமற்ற வயர்லெஸ் சாதனங்களை அணைப்பதன் மூலம் வைஃபை லேக்கை குறைக்கவும்\nஉங்கள் வைஃபை திசைவிக்கு அருகில் விளையாடி, மற்றும் இணைப்பதன் மூலம்\nஈதர்நெட் வழியாக கேம் ஹோஸ்ட் நேரடியாக நெட்வொர்க்கிற்கு.", + "explanationText": "வயர்லெஸ் கன்ட்ரோலராக ஸ்மார்ட் போன் அல்லது டேப்லெட்டைப் பயன்படுத்த,\n\"${REMOTE_APP_NAME}\" பயன்பாட்டை நிறுவவும். எந்த எண்ணிக்கையிலான சாதனங்கள்\nWi-Fi மூலம் ${APP_NAME} கேமுடன் இணைக்க முடியும், அது இலவசம்!", + "forAndroidText": "Android க்கான:", + "forIOSText": "iOS க்கு:", + "getItForText": "ஆப்பிள் ஆப் ஸ்டோரில் iOS க்கு ${REMOTE_APP_NAME} ஐப் பெறுங்கள்\nஅல்லது Google Play Store அல்லது Amazon Appstore இல் Android க்காக", + "googlePlayText": "கூகிள் பிலே", + "titleText": "மொபைல் சாதனங்களை கட்டுப்படுத்திகளாகப் பயன்படுத்துதல்:" + }, + "continuePurchaseText": "${PRICE} க்கு தொடரவா?", + "continueText": "தொடரவும்", + "controlsText": "கட்டுப்பாடுகள்", + "coopSelectWindow": { + "activenessAllTimeInfoText": "இது எல்லா நேர தரவரிசைகளுக்கும் பொருந்தாது.", + "activenessInfoText": "நீங்கள் இருக்கும் நாட்களில் இந்த பெருக்கி உயரும்\nநீங்கள் விளையாடாத நாட்களில் விளையாடுங்கள் மற்றும் குறையுங்கள்.", + "activityText": "செயல்பாடு", + "campaignText": "பிரச்சாரம்", + "challengesInfoText": "மினி-கேம்களை முடித்ததற்காக பரிசுகளைப் பெறுங்கள்.\n\nபரிசுகள் மற்றும் சிரம நிலைகள் அதிகரிக்கும்\nஒவ்வொரு முறையும் ஒரு சவால் நிறைவடைகிறது மற்றும்\nகாலாவதியாகும் போது அல்லது இழக்கப்படும் போது குறையும்.", + "challengesText": "சவால்கள்", + "currentBestText": "தற்போதைய சிறந்தது", + "customText": "தனிப்பயன்", + "entryFeeText": "நுழைவு", + "forfeitConfirmText": "இந்த சவாலை இழந்தீர்களா?", + "forfeitNotAllowedYetText": "இந்த சவாலை இன்னும் இழக்க முடியாது.", + "forfeitText": "இழப்பு", + "multipliersText": "பெருக்கிகள்", + "nextChallengeText": "அடுத்த சவால்", + "nextPlayText": "அடுத்த விளையாட்டு", + "ofTotalTimeText": "${TOTAL} இல்", + "playNowText": "இப்பொழுதே விளையாடு", + "pointsText": "புள்ளிகள்", + "powerRankingFinishedSeasonUnrankedText": "(சீசன் முடிவடையவில்லை)", + "powerRankingNotInTopText": "(மேல் ${NUMBER} இல் இல்லை)", + "powerRankingPointsEqualsText": "= ${NUMBER} pts", + "powerRankingPointsMultText": "(x ${NUMBER} புள்ளிகள்)", + "powerRankingPointsText": "${NUMBER} புள்ளிகள்", + "powerRankingPointsToRankedText": "(${REMAINING} புள்ளிகளில் ${CURRENT})", + "powerRankingText": "சக்தி தரவரிசை", + "prizesText": "பரிசுகள்", + "proMultInfoText": "${PRO} மேம்படுத்தப்பட்ட வீரர்கள்\nஇங்கே ${PERCENT}% புள்ளியைப் பெறுக.", + "seeMoreText": "மேலும்...", + "skipWaitText": "காத்திருப்பைத் தவிர்க்கவும்", + "timeRemainingText": "மீதியுள்ள நேரம்", + "toRankedText": "தரவரிசைக்கு", + "totalText": "மொத்தம்", + "tournamentInfoText": "அதிக மதிப்பெண்களுடன் போட்டியிடவும்\nஉங்கள் லீக்கில் உள்ள மற்ற வீரர்கள்.\n\nஅதிக மதிப்பெண் பெற்றவர்களுக்கு பரிசுகள் வழங்கப்படுகின்றன\nபோட்டி நேரம் முடிவடையும் போது வீரர்கள்.", + "welcome1Text": "${LEAGUE} க்கு வரவேற்கிறோம். நீங்கள் உங்கள் மேம்படுத்த முடியும்\nநட்சத்திர மதிப்பீடுகளை சம்பாதிப்பதன் மூலம் லீக் தரவரிசை, முடித்தல்\nசாதனைகள், மற்றும் போட்டிகளில் கோப்பைகளை வென்றது.", + "welcome2Text": "இதே போன்ற பல செயல்களில் இருந்து நீங்கள் டிக்கெட்டுகளைப் பெறலாம்.\nபுதிய எழுத்துக்கள், வரைபடங்கள் மற்றும் பலவற்றைத் திறக்க டிக்கெட்டுகளைப் பயன்படுத்தலாம்\nசிறு விளையாட்டுகள், போட்டிகளில் நுழைய, மற்றும் பல.", + "yourPowerRankingText": "உங்கள் சக்தி தரவரிசை:" + }, + "copyOfText": "${NAME} பிரதி", + "createEditPlayerText": "<பிளேயரை உருவாக்கவும்/திருத்தவும்>", + "createText": "உருவாக்கு", + "creditsWindow": { + "additionalAudioArtIdeasText": "கூடுதல் ஆடியோ, ஆரம்ப கலைப்படைப்பு மற்றும் யோசனைகள் ${NAME}", + "additionalMusicFromText": "${NAME} இலிருந்து கூடுதல் இசை", + "allMyFamilyText": "என் நண்பர்கள் மற்றும் குடும்பத்தினர் அனைவரும் டெஸ்ட் விளையாட உதவினார்கள்", + "codingGraphicsAudioText": "கோடிங், கிராபிக்ஸ் மற்றும் ஆடியோ: ${NAME}", + "languageTranslationsText": "மொழி மொழிபெயர்ப்புகள்:", + "legalText": "சட்ட:", + "publicDomainMusicViaText": "${NAME} வழியாக பொது டொமைன் இசை", + "softwareBasedOnText": "இந்த மென்பொருள் ${NAME} இன் பணியை அடிப்படையாகக் கொண்டது", + "songCreditText": "${TITLE} ${PERFORMER} ஆல் நிகழ்த்தப்பட்டது\n${COMPOSER} ஆல் இயற்றப்பட்டது, ${ARRANGER} ஆல் ஏற்பாடு செய்யப்பட்டது, ${PUBLISHER} ஆல் வெளியிடப்பட்டது,\nமரியாதை ${SOURCE}", + "soundAndMusicText": "ஒலி மற்றும் இசை:", + "soundsText": "ஒலிகள் (${SOURCE}):", + "specialThanksText": "சிறப்பு நன்றி:", + "thanksEspeciallyToText": "குறிப்பாக ${NAME} க்கு நன்றி", + "titleText": "${APP_NAME} வரவுகள்", + "whoeverInventedCoffeeText": "காபியை கண்டுபிடித்தவர்" + }, + "currentStandingText": "உங்கள் தற்போதைய நிலை #${RANK}", + "customizeText": "தனிப்பயனாக்கலாம்...", + "deathsTallyText": "${COUNT} உயிரிழப்புகள்", + "deathsText": "உயிரிழப்புகள்", + "debugText": "பிழைத்திருத்தம்", + "debugWindow": { + "reloadBenchmarkBestResultsText": "குறிப்பு: இதைச் சோதிக்கும்போது அமைப்புகள்-> கிராபிக்ஸ்-> அமைப்புகளை 'உயர்' என அமைக்க பரிந்துரைக்கப்படுகிறது.", + "runCPUBenchmarkText": "CPU பெஞ்ச்மார்க்கை இயக்கவும்", + "runGPUBenchmarkText": "GPU பெஞ்ச்மார்க்கை இயக்கவும்", + "runMediaReloadBenchmarkText": "மீடியா-ரீலோட் பெஞ்ச்மார்க்கை இயக்கவும்", + "runStressTestText": "மன அழுத்த சோதனையை இயக்கவும்", + "stressTestPlayerCountText": "பிளேயர் எண்ணிக்கை", + "stressTestPlaylistDescriptionText": "மன அழுத்த சோதனை பிளேலிஸ்ட்", + "stressTestPlaylistNameText": "பிளேலிஸ்ட் பெயர்", + "stressTestPlaylistTypeText": "பிளேலிஸ்ட் வகை", + "stressTestRoundDurationText": "சுற்று நேரம்", + "stressTestTitleText": "அழுத்த சோதனை", + "titleText": "வரையறைகள் மற்றும் அழுத்த சோதனைகள்", + "totalReloadTimeText": "மொத்த மறுஏற்றம் நேரம்: ${TIME} (விவரங்களுக்கு பதிவைப் பார்க்கவும்)" + }, + "defaultGameListNameText": "இயல்புநிலை ${PLAYMODE} பிளேலிஸ்ட்", + "defaultNewGameListNameText": "எனது ${PLAYMODE} பிளேலிஸ்ட்", + "deleteText": "அழி", + "demoText": "டெமோ", + "denyText": "மறுக்க", + "desktopResText": "டெஸ்க்டாப் ரெஸ்", + "difficultyEasyText": "சுலபம்", + "difficultyHardOnlyText": "கடினமான முறை மட்டுமே", + "difficultyHardText": "கடினமான", + "difficultyHardUnlockOnlyText": "இந்த நிலை கடினமான முறையில் மட்டுமே திறக்க முடியும்.\nஉங்களுக்கு என்ன தேவை என்று நீங்கள் நினைக்கிறீர்களா!?!?!", + "directBrowserToURLText": "தயவுசெய்து பின்வரும் URL க்கு ஒரு இணைய உலாவியை இயக்கவும்:", + "disableRemoteAppConnectionsText": "ரிமோட்-ஆப் இணைப்புகளை முடக்கு", + "disableXInputDescriptionText": "4 க்கும் மேற்பட்ட கட்டுப்பாட்டாளர்களை அனுமதிக்கிறது ஆனால் வேலை செய்யாமல் போகலாம்.", + "disableXInputText": "XInput ஐ முடக்கு", + "doneText": "முடிந்தது", + "drawText": "டிரா", + "duplicateText": "நகல்எடுத்தல்", + "editGameListWindow": { + "addGameText": "கூட்டு\nவிளையாட்டு", + "cantOverwriteDefaultText": "இயல்புநிலை பிளேலிஸ்ட்டை மேலெழுத முடியாது!", + "cantSaveAlreadyExistsText": "அந்த பெயருடன் ஒரு பிளேலிஸ்ட் ஏற்கனவே உள்ளது!", + "cantSaveEmptyListText": "வெற்று பிளேலிஸ்ட்டைச் சேமிக்க முடியவில்லை!", + "editGameText": "தொகு\nவிளையாட்டு", + "listNameText": "பிளேலிஸ்ட் பெயர்", + "nameText": "பெயர்", + "removeGameText": "அகற்று\nவிளையாட்டு", + "saveText": "பட்டியலைச் சேமிக்கவும்", + "titleText": "பிளேலிஸ்ட் எடிட்டர்" + }, + "editProfileWindow": { + "accountProfileInfoText": "இந்த சிறப்பு சுயவிவரத்திற்கு ஒரு பெயர் உள்ளது\nமற்றும் உங்கள் கணக்கை அடிப்படையாகக் கொண்ட ஐகான்.\n\n${ICONS}\n\nபயன்படுத்த தனிப்பயன் சுயவிவரங்களை உருவாக்கவும்\nவெவ்வேறு பெயர்கள் அல்லது தனிப்பயன் சின்னங்கள்.", + "accountProfileText": "(கணக்கு விவரம்)", + "availableText": "\"${NAME}\" என்ற பெயர் உள்ளது.", + "characterText": "குணம்", + "checkingAvailabilityText": "\"${NAME}\" க்கான இருப்பைச் சரிபார்க்கிறது...", + "colorText": "நிறம்", + "getMoreCharactersText": "அதிக கதாபாத்திரங்களைப் பெறுங்கள்...", + "getMoreIconsText": "மேலும் சின்னங்களைப் பெறுங்கள்...", + "globalProfileInfoText": "உலகளாவிய பிளேயர் சுயவிவரங்கள் தனித்துவமானவை என்று உத்தரவாதம் அளிக்கப்படுகின்றன\nஉலகளாவிய பெயர்கள். அவற்றில் தனிப்பயன் சின்னங்களும் அடங்கும்.", + "globalProfileText": "(உலக சுயவிவரம்)", + "highlightText": "முன்னிலைப்படுத்த", + "iconText": "ஐகான்", + "localProfileInfoText": "உள்ளூர் பிளேயர் சுயவிவரங்களுக்கு ஐகான்கள் இல்லை மற்றும் அவற்றின் பெயர்கள் உள்ளன\nதனிப்பட்டதாக உத்தரவாதம் இல்லை. உலகளாவிய சுயவிவரத்திற்கு மேம்படுத்தவும்\nதனித்துவமான பெயரை முன்பதிவு செய்து தனிப்பயன் ஐகானைச் சேர்க்கவும்.", + "localProfileText": "(உள்ளூர் சுயவிவரம்)", + "nameDescriptionText": "வீரரின் பெயர்", + "nameText": "பெயர்", + "randomText": "சீரற்ற", + "titleEditText": "சுயவிவரத்தைத் திருத்து", + "titleNewText": "புதிய சுயவிவரம்", + "unavailableText": "\"${NAME}\" கிடைக்கவில்லை; மற்றொரு பெயரை முயற்சிக்கவும்.", + "upgradeProfileInfoText": "இது உலகம் முழுவதும் உங்கள் பிளேயர் பெயரை முன்பதிவு செய்யும்\nமற்றும் தனிப்பயன் ஐகானை ஒதுக்க உங்களை அனுமதிக்கிறது.", + "upgradeToGlobalProfileText": "உலகளாவிய சுயவிவரத்திற்கு மேம்படுத்தவும்" + }, + "editSoundtrackWindow": { + "cantDeleteDefaultText": "இயல்புநிலை ஒலிப்பதிவை நீங்கள் நீக்க முடியாது.", + "cantEditDefaultText": "இயல்புநிலை ஒலிப்பதிவை திருத்த முடியாது. அதை நகலெடுக்கவும் அல்லது புதிய ஒன்றை உருவாக்கவும்.", + "cantOverwriteDefaultText": "இயல்புநிலை ஒலிப்பதிவை மேலெழுத முடியாது", + "cantSaveAlreadyExistsText": "அந்த பெயருடன் ஒரு ஒலிப்பதிவு ஏற்கனவே உள்ளது!", + "defaultGameMusicText": "<இயல்பான விளையாட்டு இசை>", + "defaultSoundtrackNameText": "இயல்பு ஒலிப்பதிவு", + "deleteConfirmText": "ஒலிப்பதிவை நீக்கு:\n\n'${NAME}'?", + "deleteText": "அழி\nஒலிப்பதிவு", + "duplicateText": "நகல்\nஒலிப்பதிவு", + "editSoundtrackText": "ஒலிப்பதிவு எடிட்டர்", + "editText": "தொகு\nஒலிப்பதிவு", + "fetchingITunesText": "மியூசிக் ஆப் பிளேலிஸ்ட்களைப் பெறுகிறது...", + "musicVolumeZeroWarning": "எச்சரிக்கை: இசை அளவு 0 ஆக அமைக்கப்பட்டுள்ளது", + "nameText": "பெயர்", + "newSoundtrackNameText": "எனது ஒலிப்பதிவு ${COUNT}", + "newSoundtrackText": "புதிய ஒலிப்பதிவு:", + "newText": "புதிய\nஒலிப்பதிவு", + "selectAPlaylistText": "ஒரு பிளேலிஸ்ட்டைத் தேர்ந்தெடுக்கவும்", + "selectASourceText": "இசை ஆதாரம்", + "testText": "சோதனை", + "titleText": "ஒலிப்பதிவுகள்", + "useDefaultGameMusicText": "இயல்புநிலை விளையாட்டு இசை", + "useITunesPlaylistText": "இசை ஆப் பிளேலிஸ்ட்", + "useMusicFileText": "இசை கோப்பு (mp3, போன்றவை)", + "useMusicFolderText": "இசை கோப்புகளின் கோப்புறை" + }, + "editText": "மாற்று", + "endText": "முற்று", + "enjoyText": "மகிழ்!", + "epicDescriptionFilterText": "${DESCRIPTION} காவிய மெதுவான இயக்கத்தில்.", + "epicNameFilterText": "காவியம் ${NAME}", + "errorAccessDeniedText": "அணுகல் மறுக்கப்பட்டது", + "errorOutOfDiskSpaceText": "வட்டு இடத்திற்கு வெளியே", + "errorText": "பிழை", + "errorUnknownText": "அறியப்படாத பிழை", + "exitGameText": "${APP_NAME} இலிருந்து வெளியேறவா?", + "exportSuccessText": "'${NAME}' ஏற்றுமதி செய்யப்பட்டது.", + "externalStorageText": "வெளிப்புற சேமிப்பு", + "failText": "தோல்வி", + "fatalErrorText": "அட டா; ஏதாவது காணவில்லை அல்லது உடைந்திருக்கிறது.\nதயவுசெய்து பயன்பாட்டை மீண்டும் நிறுவ முயற்சிக்கவும் அல்லது\nஉதவிக்கு ${EMAIL} ஐ தொடர்பு கொள்ளவும்.", + "fileSelectorWindow": { + "titleFileFolderText": "ஒரு கோப்பு அல்லது கோப்புறையைத் தேர்ந்தெடுக்கவும்", + "titleFileText": "ஒரு கோப்பைத் தேர்ந்தெடுக்கவும்", + "titleFolderText": "ஒரு கோப்புறையைத் தேர்ந்தெடுக்கவும்", + "useThisFolderButtonText": "இந்த கோப்புறையைப் பயன்படுத்தவும்" + }, + "filterText": "வடிகட்டி", + "finalScoreText": "இறுதி மதிப்பெண்", + "finalScoresText": "இறுதி மதிப்பெண்கள்", + "finalTimeText": "இறுதி நேரம்", + "finishingInstallText": "நிறுவலை முடித்தல்; ஒரு நிமிடம்...", + "fireTVRemoteWarningText": "* சிறந்த அனுபவத்திற்கு, பயன்படுத்தவும்\nவிளையாட்டு கட்டுப்பாட்டாளர்கள் அல்லது நிறுவ\nஉங்கள் '${REMOTE_APP_NAME}' பயன்பாடு\nதொலைபேசிகள் இன்ஸ்டால் செய்யவும்", + "firstToFinalText": "முதலில்-${COUNT} இறுதி", + "firstToSeriesText": "முதல்-${COUNT} தொடர்", + "fiveKillText": "ஐந்து கொலை!!!", + "flawlessWaveText": "குறைபாடற்ற அலை!", + "fourKillText": "நான்கு கொலை!!!", + "friendScoresUnavailableText": "நண்பர் மதிப்பெண்கள் கிடைக்கவில்லை.", + "gameCenterText": "GameCenter", + "gameCircleText": "GameCircle", + "gameLeadersText": "விளையாட்டு ${COUNT} தலைவர்கள்", + "gameListWindow": { + "cantDeleteDefaultText": "இயல்புநிலை பிளேலிஸ்ட்டை நீங்கள் நீக்க முடியாது.", + "cantEditDefaultText": "இயல்புநிலை பிளேலிஸ்ட்டைத் திருத்த முடியாது! அதை நகலெடுக்கவும் அல்லது புதிய ஒன்றை உருவாக்கவும்.", + "cantShareDefaultText": "இயல்புநிலை பிளேலிஸ்ட்டை நீங்கள் பகிர முடியாது.", + "deleteConfirmText": "\"${LIST}\" ஐ நீக்கவா?", + "deleteText": "நீக்கு\nபிளேலிஸ்ட்", + "duplicateText": "நகல்எடு்\nபிளேலிஸ்ட்", + "editText": "தொகு\nபிளேலிஸ்ட்", + "newText": "புதிய\nபிளேலிஸ்ட்", + "showTutorialText": "டுடோரியலைக் காட்டு", + "shuffleGameOrderText": "விளையாட்டு ஆர்டரை கலக்கவும்", + "titleText": "${TYPE} பிளேலிஸ்ட்களைத் தனிப்பயனாக்கவும்" + }, + "gameSettingsWindow": { + "addGameText": "விளையாட்டைச் சேர்க்கவும்" + }, + "gamesToText": "${WINCOUNT} விளையாட்டுகள் ${LOSECOUNT} க்கு", + "gatherWindow": { + "aboutDescriptionLocalMultiplayerExtraText": "நினைவில் கொள்ளுங்கள்: ஒரு பார்ட்டியின் எந்த சாதனமும் அதிகமாக இருக்கலாம்\nஉங்களிடம் போதுமான கட்டுப்படுத்திகள் இருந்தால் ஒரு வீரரை விட.", + "aboutDescriptionText": "ஒரு பார்ட்டியைக் கூட்ட இந்த தாவல்களைப் பயன்படுத்தவும்\n\nவிளையாட்டுகள் மற்றும் போட்டிகளில் விளையாட பார்ட்டிகளை உங்களை அனுமதிக்கின்றன\nவெவ்வேறு சாதனங்களில் உங்கள் நண்பர்களுடன்.\n\nமேல் வலதுபுறத்தில் உள்ள ${PARTY} பொத்தானைப் பயன்படுத்தவும்\nஉங்கள் பார்ட்டி உடன் அரட்டையடிக்கவும் தொடர்பு கொள்ளவும்.\n(ஒரு கட்டுப்படுத்தியில், ஒரு மெனுவில் இருக்கும்போது ${BUTTON} ஐ அழுத்தவும்)", + "aboutText": "இது பற்றி", + "addressFetchErrorText": "<முகவரிகளைப் பெறுவதில் பிழை>", + "appInviteMessageText": "${NAME} உங்களுக்கு ${APP_NAME} இல் ${COUNT} டிக்கெட்டுகளை அனுப்பியுள்ளார்", + "appInviteSendACodeText": "அவர்களுக்கு ஒரு குறியீட்டை அனுப்பவும்", + "appInviteTitleText": "${APP_NAME} ஆப் அழைப்பு", + "bluetoothAndroidSupportText": "(ப்ளூடூத்தை ஆதரிக்கும் எந்த ஆண்ட்ராய்டு சாதனத்திலும் வேலை செய்யும்)", + "bluetoothDescriptionText": "ப்ளூடூத் மூலம் ஒரு பார்ட்டி நடத்துங்கள்/சேருங்கள்:", + "bluetoothHostText": "ப்ளூடூத் மூலம் ஹோஸ்ட் செய்யவும்", + "bluetoothJoinText": "ப்ளூடூத் மூலம் சேரவும்", + "bluetoothText": "புளூடூத்", + "checkingText": "சரிபார்க்கிறது...", + "copyCodeConfirmText": "குறியீடு கிளிப்போர்டுக்கு நகலெடுக்கப்பட்டது.", + "copyCodeText": "நகல் குறியீடு", + "dedicatedServerInfoText": "சிறந்த முடிவுகளுக்கு, ஒரு பிரத்யேக சர்வரை அமைக்கவும். எப்படி என்பதை அறிய bombsquadgame.com/server ஐ பார்க்கவும்.", + "disconnectClientsText": "இது ${COUNT} பிளேயர் (களை) துண்டிக்கும்\nஉங்கள் கட்சியில். நீங்கள் சொல்வது உறுதியா?", + "earnTicketsForRecommendingAmountText": "அவர்கள் விளையாட்டை முயற்சித்தால் நண்பர்கள் ${COUNT} டிக்கெட்டுகளைப் பெறுவார்கள்\n(மேலும் ஒவ்வொருவருக்கும் நீங்கள் ${YOU_COUNT} பெறுவீர்கள்)", + "earnTicketsForRecommendingText": "விளையாட்டைப் பகிரவும்\nஇலவச டிக்கெட்டுகளுக்கு", + "emailItText": "அதை மின்னஞ்சல் செய்யவும்", + "favoritesSaveText": "பிடித்தவையாக சேமி", + "favoritesText": "பிடித்தவை", + "freeCloudServerAvailableMinutesText": "அடுத்த இலவச கிளவுட் சர்வர் ${MINUTES} நிமிடங்களில் கிடைக்கும்.", + "freeCloudServerAvailableNowText": "இலவச கிளவுட் சர்வர் கிடைக்கிறது!", + "freeCloudServerNotAvailableText": "இலவச கிளவுட் சேவையகங்கள் இல்லை.", + "friendHasSentPromoCodeText": "${NAME} இலிருந்து ${COUNT} ${APP_NAME} டிக்கெட்டுகள்", + "friendPromoCodeAwardText": "ஒவ்வொரு முறையும் நீங்கள் பயன்படுத்தும்போது ${COUNT} டிக்கெட்டுகளைப் பெறுவீர்கள்.", + "friendPromoCodeExpireText": "குறியீடு ${EXPIRE_HOURS} மணிநேரத்தில் காலாவதியாகும் மற்றும் புதிய பிளேயர்களுக்கு மட்டுமே வேலை செய்யும்.", + "friendPromoCodeInstructionsText": "இதைப் பயன்படுத்த, ${APP_NAME} ஐத் திறந்து \"அமைப்புகள்-> மேம்பட்ட-> குறியீட்டை உள்ளிடவும்\" என்பதற்குச் செல்லவும்.\nஆதரிக்கப்படும் அனைத்து தளங்களுக்கும் பதிவிறக்க இணைப்புகளுக்கு bombsquadgame.com ஐப் பார்க்கவும்.", + "friendPromoCodeRedeemLongText": "இதை ${MAX_USES} பேர் வரை ${COUNT} இலவச டிக்கெட்டுகளுக்கு மீட்டெடுக்கலாம்.", + "friendPromoCodeRedeemShortText": "விளையாட்டில் ${COUNT} டிக்கெட்டுகளுக்கு அதை மீட்டெடுக்கலாம்.", + "friendPromoCodeWhereToEnterText": "(\"அமைப்புகள்-> மேம்பட்ட-> குறியீட்டை உள்ளிடவும்\")", + "getFriendInviteCodeText": "நண்பர் அழைப்புக் குறியீட்டைப் பெறுங்கள்", + "googlePlayDescriptionText": "உங்கள் விருந்துக்கு Google Play பிளேயர்களை அழைக்கவும்:", + "googlePlayInviteText": "அழை", + "googlePlayReInviteText": "உங்கள் பார்ட்டியில் ${COUNT} Google Play பிளேயர் (கள்) உள்ளனர்\nநீங்கள் ஒரு புதிய அழைப்பைத் தொடங்கினால் யார் துண்டிக்கப்படுவார்கள்.\nஅவர்களை திரும்ப பெற புதிய அழைப்பிதழில் சேர்க்கவும்.", + "googlePlaySeeInvitesText": "அழைப்புகளைப் பார்க்கவும்", + "googlePlayText": "கூகிள் Play", + "googlePlayVersionOnlyText": "(Android / Google Play பதிப்பு)", + "hostPublicPartyDescriptionText": "ஒரு பொது பார்ட்டி நடத்துங்கள்", + "hostingUnavailableText": "ஹோஸ்டிங் கிடைக்கவில்லை", + "inDevelopmentWarningText": "குறிப்பு:\n\nநெட்வொர்க் ப்ளே ஒரு புதிய மற்றும் இன்னும் வளர்ந்து வரும் அம்சமாகும்.\nஇப்போதைக்கு, அனைவருக்கும் இது மிகவும் பரிந்துரைக்கப்படுகிறது\nபிளேயர்கள் ஒரே வைஃபை நெட்வொர்க்கில் இருக்க வேண்டும்.", + "internetText": "இணையதளம்", + "inviteAFriendText": "நண்பர்களுக்கு விளையாட்டு இல்லையா? அவர்களை அழைக்கவும்\nமுயற்சித்துப் பாருங்கள், அவர்கள் ${COUNT} இலவச டிக்கெட்டுகளைப் பெறுவார்கள்.", + "inviteFriendsText": "நண்பர்களை அழைக்க", + "joinPublicPartyDescriptionText": "பொது பார்ட்டியில் சேருங்கள்", + "localNetworkDescriptionText": "அருகிலுள்ள பார்ட்டியில் சேருங்கள் (LAN, ப்ளூடூத், முதலியன)", + "localNetworkText": "உள்ளூர் நெட்வொர்க்", + "makePartyPrivateText": "எனது பார்ட்டியை தனிப்பட்டதாக்குங்கள்", + "makePartyPublicText": "எனது பார்ட்டியை பகிரங்கப்படுத்துங்கள்", + "manualAddressText": "முகவரி", + "manualConnectText": "இணை", + "manualDescriptionText": "முகவரி மூலம் ஒரு பார்ட்டியில் சேருங்கள்:", + "manualJoinSectionText": "முகவரி மூலம் சேருங்கள்", + "manualJoinableFromInternetText": "நீங்கள் இணையத்தில் சேர முடியுமா?", + "manualJoinableNoWithAsteriskText": "இல்லை*", + "manualJoinableYesText": "ஆம்", + "manualRouterForwardingText": "*இதைச் சரிசெய்ய, உங்கள் உள்ளூர் முகவரிக்கு UDP போர்ட் ${PORT} ஐ அனுப்ப உங்கள் திசைவியை உள்ளமைக்க முயற்சிக்கவும்", + "manualText": "கையேடு", + "manualYourAddressFromInternetText": "இணையத்திலிருந்து உங்கள் முகவரி:", + "manualYourLocalAddressText": "உங்கள் உள்ளூர் முகவரி:", + "nearbyText": "அருகில்", + "noConnectionText": "<இணைப்பு இல்லை>", + "otherVersionsText": "(பிற பதிப்புகள்)", + "partyCodeText": "பார்ட்டி குறியீடு", + "partyInviteAcceptText": "ஏற்றுக்கொள்", + "partyInviteDeclineText": "மறு", + "partyInviteGooglePlayExtraText": "('கூடி' சாளரத்தில் 'Google Play' தாவலைப் பார்க்கவும்)", + "partyInviteIgnoreText": "புறக்கணிக்கவும்", + "partyInviteText": "${NAME} அழைத்துள்ளார்\nநீங்கள் அவர்களின் பார்ட்டியில் சேர!", + "partyNameText": "பார்ட்டி பெயர்", + "partyServerRunningText": "உங்கள் பார்ட்டி சர்வர் இயங்குகிறது.", + "partySizeText": "பார்ட்டி அளவு", + "partyStatusCheckingText": "நிலையை சரிபார்க்கிறது...", + "partyStatusJoinableText": "உங்கள் பார்ட்டி இப்போது இணையத்தில் சேரக்கூடியது", + "partyStatusNoConnectionText": "சேவையகத்துடன் இணைக்க முடியவில்லை", + "partyStatusNotJoinableText": "உங்கள் பார்ட்டி இணையத்தில் சேர முடியாது", + "partyStatusNotPublicText": "உங்கள் பார்ட்டி பொது அல்ல", + "pingText": "பிங்", + "portText": "போர்ட்", + "privatePartyCloudDescriptionText": "தனிப்பட்ட பார்ட்டிகள் பிரத்யேக கிளவுட் சேவையகங்களில் இயங்குகின்றன; திசைவி கட்டமைப்பு தேவையில்லை.", + "privatePartyHostText": "ஒரு தனியார் பார்ட்டி நடத்துங்கள்", + "privatePartyJoinText": "ஒரு தனியார் பார்ட்டியில் சேருங்கள்", + "privateText": "தனியார்", + "publicHostRouterConfigText": "இதற்கு உங்கள் ரூட்டரில் போர்ட்-ஃபார்வர்டிங் கட்டமைக்க வேண்டும். எளிதான விருப்பத்திற்கு, ஒரு தனியார் பார்ட்டி நடத்துங்கள்.", + "publicText": "பொது", + "requestingAPromoCodeText": "குறியீட்டைக் கோருகிறது...", + "sendDirectInvitesText": "நேரடி அழைப்புகளை அனுப்பவும்", + "shareThisCodeWithFriendsText": "இந்த குறியீட்டை நண்பர்களுடன் பகிர்ந்து கொள்ளுங்கள்:", + "showMyAddressText": "என் முகவரியை காட்டு", + "startHostingPaidText": "இப்போது ${COST} க்கு ஹோஸ்ட் செய்யுங்கள்", + "startHostingText": "ஹோஸ்ட்", + "startStopHostingMinutesText": "அடுத்த ${MINUTES} நிமிடங்களுக்கு நீங்கள் இலவசமாக ஹோஸ்டிங்கை ஆரம்பித்து நிறுத்தலாம்.", + "stopHostingText": "ஹோஸ்டிங்கை நிறுத்து", + "titleText": "சேகரிக்கவும்", + "wifiDirectDescriptionBottomText": "எல்லா சாதனங்களிலும் 'வைஃபை டைரக்ட்' பேனல் இருந்தால், அவற்றைக் கண்டுபிடிக்க அவற்றைப் பயன்படுத்த முடியும்\nமற்றும் ஒருவருக்கொருவர் இணைக்கவும். எல்லா சாதனங்களும் இணைக்கப்பட்டவுடன், நீங்கள் கட்சிகளை உருவாக்கலாம்\nஇங்கே 'உள்ளூர் நெட்வொர்க்' தாவலைப் பயன்படுத்தி, வழக்கமான Wi-Fi நெட்வொர்க்கைப் போலவே.\n\nசிறந்த முடிவுகளுக்கு, வைஃபை டைரக்ட் ஹோஸ்ட் ${APP_NAME} பார்ட்டி ஹோஸ்டாகவும் இருக்க வேண்டும்.", + "wifiDirectDescriptionTopText": "ஆண்ட்ராய்டு சாதனங்களை நேரடியாக இல்லாமல் இணைக்க வைஃபை டைரக்ட் பயன்படுத்தலாம்\nவைஃபை நெட்வொர்க் தேவை. இது Android 4.2 அல்லது புதியவற்றில் சிறப்பாகச் செயல்படும்.\n\nஇதைப் பயன்படுத்த, வைஃபை அமைப்புகளைத் திறந்து மெனுவில் 'வைஃபை டைரக்ட்' ஐப் பார்க்கவும்.", + "wifiDirectOpenWiFiSettingsText": "வைஃபை அமைப்புகளைத் திறக்கவும்", + "wifiDirectText": "வைஃபை நேரடி", + "worksBetweenAllPlatformsText": "(அனைத்து தளங்களுக்கும் இடையில் வேலை செய்கிறது)", + "worksWithGooglePlayDevicesText": "(விளையாட்டின் கூகிள் பிளே (ஆண்ட்ராய்டு) பதிப்பில் இயங்கும் சாதனங்களுடன் வேலை செய்கிறது)", + "youHaveBeenSentAPromoCodeText": "உங்களுக்கு ${APP_NAME} விளம்பர குறியீடு அனுப்பப்பட்டுள்ளது:" + }, + "getTicketsWindow": { + "freeText": "இலவசம்!", + "freeTicketsText": "இலவச டிக்கெட்டுகள்", + "inProgressText": "ஒரு பரிவர்த்தனை நடந்து கொண்டிருக்கிறது; சிறிது நேரத்தில் மீண்டும் முயற்சிக்கவும்.", + "purchasesRestoredText": "கொள்முதல் மீட்டெடுக்கப்பட்டது.", + "receivedTicketsText": "${COUNT} டிக்கெட்டுகள் பெறப்பட்டன!", + "restorePurchasesText": "கொள்முதலை திரும்பப்பெறு", + "ticketPack1Text": "சிறிய டிக்கெட் பேக்", + "ticketPack2Text": "நடுத்தர டிக்கெட் பேக்", + "ticketPack3Text": "பெரிய டிக்கெட் பேக்", + "ticketPack4Text": "ஜம்போ டிக்கெட் பேக்", + "ticketPack5Text": "மம்மத் டிக்கெட் பேக்", + "ticketPack6Text": "அல்டிமேட் டிக்கெட் பேக்", + "ticketsFromASponsorText": "${COUNT} டிக்கெட்டுகளைப் பெறுங்கள்\nஒரு ஸ்பான்சரிடமிருந்து", + "ticketsText": "${COUNT} டிக்கெட்டுகள்", + "titleText": "டிக்கெட்டுகளைப் பெறுங்கள்", + "unavailableLinkAccountText": "மன்னிக்கவும், இந்த தளத்தில் கொள்முதல் கிடைக்கவில்லை.\nஒரு தீர்வாக, இந்தக் கணக்கை ஒரு கணக்குடன் இணைக்கலாம்\nமற்றொரு தளம் மற்றும் அங்கு கொள்முதல் செய்யுங்கள்.", + "unavailableTemporarilyText": "இது தற்போது கிடைக்கவில்லை; தயவுசெய்து பிறகு முயற்சிக்கவும்.", + "unavailableText": "மன்னிக்கவும், இது கிடைக்கவில்லை.", + "versionTooOldText": "மன்னிக்கவும், விளையாட்டின் இந்தப் பதிப்பு மிகவும் பழையது; தயவுசெய்து புதிய ஒன்றை புதுப்பிக்கவும்.", + "youHaveShortText": "உங்களிடம் ${COUNT} உள்ளது", + "youHaveText": "உங்களிடம் ${COUNT} டிக்கெட்டுகள் உள்ளன" + }, + "googleMultiplayerDiscontinuedText": "மன்னிக்கவும், கூகுளின் மல்டிபிளேயர் சேவை இனி கிடைக்காது.\nநான் முடிந்தவரை விரைவாக மாற்றுவதற்கு வேலை செய்கிறேன்.\nஅதுவரை, வேறு இணைப்பு முறையை முயற்சிக்கவும்.\n-எரிக்", + "googlePlayText": "கூகுள் பிளே", + "graphicsSettingsWindow": { + "alwaysText": "எப்போதும்", + "fullScreenCmdText": "முழுத்திரை (Cmd-F)", + "fullScreenCtrlText": "முழுத்திரை (Ctrl-F)", + "gammaText": "காமா", + "highText": "உயர்", + "higherText": "அதிக", + "lowText": "குறைந்த", + "mediumText": "நடுத்தர", + "neverText": "ஒருபோதும்", + "resolutionText": "பகுத்தல்", + "showFPSText": "FPS ஐக் காட்டு", + "texturesText": "இழைமங்கள்", + "titleText": "கிராபிக்ஸ்", + "tvBorderText": "டிவி பார்டர்", + "verticalSyncText": "செங்குத்தான ஒத்திசை", + "visualsText": "காட்சிகள்" + }, + "helpWindow": { + "bombInfoText": "- வெடிகுண்டு -\nகுத்துக்களை விட வலிமையானது, ஆனால்\nகடுமையான சுய காயம் ஏற்படலாம்.\nசிறந்த முடிவுகளுக்கு, நோக்கி எறியுங்கள்\nஉருகி முடிவதற்குள் எதிரி.", + "bombInfoTextScale": 0.6, + "canHelpText": "${APP_NAME} உதவலாம்.", + "controllersInfoText": "நெட்வொர்க் அல்லது நண்பர்களுடன் நீங்கள் ${APP_NAME} ஐ விளையாடலாம்\nஉங்களிடம் போதுமான கட்டுப்பாட்டாளர்கள் இருந்தால் அனைவரும் ஒரே சாதனத்தில் விளையாடலாம்.\n${APP_NAME} பல்வேறு வகைகளை ஆதரிக்கிறது; நீங்கள் தொலைபேசிகளைப் பயன்படுத்தலாம்\nஇலவச '${REMOTE_APP_NAME}' பயன்பாட்டின் மூலம் கட்டுப்படுத்திகளாக.\nமேலும் தகவலுக்கு அமைப்புகள்-> கட்டுப்பாட்டாளர்கள் பார்க்கவும்.", + "controllersInfoTextRemoteOnly": "நெட்வொர்க் அல்லது நண்பர்களுடன் நீங்கள் ${APP_NAME} ஐ விளையாடலாம்\nபோன்களை பயன்படுத்தி அனைவரும் ஒரே சாதனத்தில் விளையாடலாம்\nஇலவச '${REMOTE_APP_NAME}' பயன்பாட்டின் மூலம் கட்டுப்படுத்திகள்.", + "controllersText": "கட்டுப்பாட்டாளர்கள்", + "controlsSubtitleText": "உங்கள் நட்பான ${APP_NAME} எழுத்து சில அடிப்படை செயல்களைக் கொண்டுள்ளது:", + "controlsText": "கட்டுப்பாடுகள்", + "devicesInfoText": "நெட்வொர்க்கில் ${APP_NAME} இன் VR பதிப்பை இயக்கலாம்\nவழக்கமான பதிப்பு, எனவே உங்கள் கூடுதல் தொலைபேசிகள், மாத்திரைகள்,\nமற்றும் கணினிகள் மற்றும் உங்கள் விளையாட்டு கிடைக்கும். இது கூட பயனுள்ளதாக இருக்கும்\nவிளையாட்டின் வழக்கமான பதிப்பை விஆர் பதிப்போடு இணைக்கவும்\nவெளியில் உள்ளவர்களை செயலைப் பார்க்க அனுமதிக்கவும்.", + "devicesText": "சாதனங்கள்", + "friendsGoodText": "இவை இருப்பது நல்லது. ${APP_NAME} பலவற்றில் மிகவும் வேடிக்கையாக உள்ளது\nவீரர்கள் மற்றும் ஒரே நேரத்தில் 8 வரை ஆதரிக்க முடியும், இது எங்களை இட்டுச் செல்கிறது:", + "friendsText": "நண்பர்கள்", + "jumpInfoText": "- தாவு -\nசிறிய இடைவெளிகளைக் கடக்கச் செல்லவும்,\nபொருள்களை மேலே தூக்கி எறிய, மற்றும்\nமகிழ்ச்சியின் உணர்வுகளை வெளிப்படுத்த.", + "orPunchingSomethingText": "அல்லது எதையாவது குத்துவது, ஒரு குன்றிலிருந்து தூக்கி எறிவது, மற்றும் ஒட்டும் குண்டு மூலம் கீழே செல்லும் வழியில் அதை ஊதுவது.", + "pickUpInfoText": "- எடு -\nகொடிகள், எதிரிகள் அல்லது எதையும் பிடிக்கவும்\nமற்றபடி தரையில் ஒட்டப்படவில்லை.\nவீசுவதற்கு மீண்டும் அழுத்தவும்.", + "powerupBombDescriptionText": "நீங்கள் மூன்று குண்டுகளை வெளியேற்றலாம்\nஒன்றுக்கு பதிலாக ஒரு வரிசையில்.", + "powerupBombNameText": "மூன்று-குண்டுகள்", + "powerupCurseDescriptionText": "ஒருவேளை நீங்கள் இவற்றைத் தவிர்க்க விரும்புகிறீர்கள்.\n... அல்லது நீங்கள்?", + "powerupCurseNameText": "சாபம்", + "powerupHealthDescriptionText": "உங்களை முழு உயிர் மீட்டெடுக்கிறது.\nநீங்கள் ஒருபோதும் யூகித்திருக்க மாட்டீர்கள்.", + "powerupHealthNameText": "மெட்-பேக்", + "powerupIceBombsDescriptionText": "சாதாரண குண்டுகளை விட பலவீனமானது\nஆனால் உங்கள் எதிரிகளை உறைய விடவும்\nமற்றும் குறிப்பாக உடையக்கூடியது.", + "powerupIceBombsNameText": "பனி-குண்டுகள்", + "powerupImpactBombsDescriptionText": "வழக்கமானதை விட சற்று பலவீனமானது\nகுண்டுகள், ஆனால் அவை தாக்கத்தில் வெடிக்கின்றன.", + "powerupImpactBombsNameText": "தூண்டுதல்-குண்டுகள்", + "powerupLandMinesDescriptionText": "இவை 3 பொதிகளில் வருகின்றன;\nஅடிப்படை பாதுகாப்புக்கு அல்லது\nவிரைவான எதிரிகளை நிறுத்துதல்.", + "powerupLandMinesNameText": "நில-குண்டு", + "powerupPunchDescriptionText": "உங்கள் குத்துக்களை கடினமாக்குகிறது,\nவேகமான, சிறந்த, வலுவான.", + "powerupPunchNameText": "குத்துச்சண்டை-கையுறைகள்", + "powerupShieldDescriptionText": "சிறிது சேதத்தை உறிஞ்சுகிறது\nஎனவே நீங்கள் செய்ய வேண்டியதில்லை.", + "powerupShieldNameText": "ஆற்றல்-கவசம்", + "powerupStickyBombsDescriptionText": "அவர்கள் அடிக்கும் எதையும் ஒட்டிக்கொள்க.\nபெருங்களிப்பு ஏற்படுகிறது.", + "powerupStickyBombsNameText": "ஒட்டும்-குண்டுகள்", + "powerupsSubtitleText": "நிச்சயமாக, பவர்அப் இல்லாமல் எந்த விளையாட்டும் முழுமையடையாது:", + "powerupsText": "பவர்அப்ஸ்", + "punchInfoText": "- பஞ்ச் -\nகுத்துக்கள் அதிக சேதத்தை ஏற்படுத்துகின்றன\nஉங்கள் முஷ்டிகள் வேகமாக நகர்கின்றன, எனவே\nஒரு பைத்தியக்காரனைப் போல ஓடி சுழலும்.", + "runInfoText": "- ஓடு -\nஇயக்க எந்த பொத்தானையும் அழுத்தவும். தூண்டுதல்கள் அல்லது தோள்பட்டை பட்டன்கள் உங்களிடம் இருந்தால் நன்றாக வேலை செய்யும்.\nஓடுவது உங்கள் இடங்களை வேகமாகப் பெறுகிறது, ஆனால் திரும்புவதை கடினமாக்குகிறது, எனவே பாறைகளைக் கவனியுங்கள்.", + "someDaysText": "சில நாட்களில் நீங்கள் ஏதாவது குத்துவது போல் உணர்கிறீர்கள். அல்லது எதையாவது ஊதுவது.", + "titleText": "${APP_NAME} உதவி", + "toGetTheMostText": "இந்த விளையாட்டை அதிகம் பயன்படுத்த, உங்களுக்கு இது தேவைப்படும்:", + "welcomeText": "${APP_NAME} க்கு வரவேற்கிறோம்!" + }, + "holdAnyButtonText": "<எந்த பட்டனையும் பிடி>", + "holdAnyKeyText": "<எந்த விசையையும் பிடி>", + "hostIsNavigatingMenusText": "- ${HOST} மெனுக்களை ஒரு முதலாளியைப் போல வழிநடத்துகிறது -", + "importPlaylistCodeInstructionsText": "இந்த பிளேலிஸ்ட்டை வேறு இடத்தில் இறக்குமதி செய்ய பின்வரும் குறியீட்டைப் பயன்படுத்தவும்:", + "importPlaylistSuccessText": "இறக்குமதி செய்யப்பட்ட ${TYPE} பிளேலிஸ்ட் '${NAME}'", + "importText": "இறக்குமதி", + "importingText": "இறக்குமதி செய்கிறது...", + "inGameClippedNameText": "விளையாட்டில் இருக்கும்\n\"${NAME}\"", + "installDiskSpaceErrorText": "பிழை: நிறுவலை முடிக்க முடியவில்லை.\nஉங்கள் சாதனத்தில் இடம் இல்லாமல் இருக்கலாம்.\nசிறிது இடத்தை அழித்துவிட்டு மீண்டும் முயற்சிக்கவும்.", + "internal": { + "arrowsToExitListText": "பட்டியலில் இருந்து வெளியேற ${LEFT} அல்லது ${RIGHT} ஐ அழுத்தவும்", + "buttonText": "பட்டன்", + "cantKickHostError": "நீங்கள் ஹோஸ்டை வெளியேற முடியாது.", + "chatBlockedText": "${NAME} ${TIME} வினாடிகளுக்கு அரட்டை-தடுக்கப்பட்டது.", + "connectedToGameText": "${NAME} இல் சேர்ந்தது", + "connectedToPartyText": "${NAME} இன் பார்ட்டியில் சேர்ந்தது", + "connectingToPartyText": "இணைக்கிறது...", + "connectionFailedHostAlreadyInPartyText": "இணைப்பு தோல்வியடைந்தது; ஹோஸ்ட் மற்றொரு பார்ட்டியில் இருக்கிறார்.", + "connectionFailedPartyFullText": "இணைப்பு தோல்வியடைந்தது; பார்ட்டி நிரம்பியுள்ளது.", + "connectionFailedText": "இணைப்பு தோல்வியடைந்தது", + "connectionFailedVersionMismatchText": "இணைப்பு தோல்வியடைந்தது; ஹோஸ்ட் விளையாட்டின் வேறுபட்ட பதிப்பை இயக்குகிறார்.\nநீங்கள் இருவரும் புதுப்பித்த நிலையில் இருப்பதை உறுதிசெய்து மீண்டும் முயற்சிக்கவும்.", + "connectionRejectedText": "இணைப்பு நிராகரிக்கப்பட்டது.", + "controllerConnectedText": "${CONTROLLER} இணைக்கப்பட்டுள்ளது.", + "controllerDetectedText": "1 controller detected.", + "controllerDisconnectedText": "${CONTROLLER} துண்டிக்கப்பட்டது.", + "controllerDisconnectedTryAgainText": "${CONTROLLER} துண்டிக்கப்பட்டது. மீண்டும் இணைக்க முயற்சிக்கவும்.", + "controllerForMenusOnlyText": "இந்த கட்டுப்படுத்தியை விளையாட பயன்படுத்த முடியாது; மெனுக்களில் செல்லவும்.", + "controllerReconnectedText": "${CONTROLLER} மீண்டும் இணைக்கப்பட்டது.", + "controllersConnectedText": "${COUNT} கட்டுப்படுத்திகள் இணைக்கப்பட்டுள்ளன.", + "controllersDetectedText": "${COUNT} கட்டுப்படுத்திகள் கண்டறியப்பட்டன.", + "controllersDisconnectedText": "${COUNT} கட்டுப்படுத்திகள் துண்டிக்கப்பட்டன.", + "corruptFileText": "ஊழல் கோப்பு (கள்) கண்டறியப்பட்டது. மீண்டும் நிறுவ முயற்சிக்கவும் அல்லது மின்னஞ்சல் ${EMAIL}", + "errorPlayingMusicText": "இசையை இயக்குவதில் பிழை: ${MUSIC}", + "errorResettingAchievementsText": "ஆன்லைன் சாதனைகளை மீட்டமைக்க முடியவில்லை; தயவுசெய்து பிறகு முயற்சிக்கவும்.", + "hasMenuControlText": "${NAME} மெனு கட்டுப்பாட்டைக் கொண்டுள்ளது.", + "incompatibleNewerVersionHostText": "ஹோஸ்ட் விளையாட்டின் புதிய பதிப்பை இயக்குகிறது.\nசமீபத்திய பதிப்பைப் புதுப்பித்து மீண்டும் முயற்சிக்கவும்.", + "incompatibleVersionHostText": "ஹோஸ்ட் விளையாட்டின் வேறுபட்ட பதிப்பை இயக்குகிறார்.\nநீங்கள் இருவரும் புதுப்பித்த நிலையில் இருப்பதை உறுதிசெய்து மீண்டும் முயற்சிக்கவும்.", + "incompatibleVersionPlayerText": "${NAME} விளையாட்டின் வேறு பதிப்பை இயக்குகிறார்.\nநீங்கள் இருவரும் புதுப்பித்த நிலையில் இருப்பதை உறுதிசெய்து மீண்டும் முயற்சிக்கவும்.", + "invalidAddressErrorText": "பிழை: தவறான முகவரி.", + "invalidNameErrorText": "பிழை: தவறான பெயர்.", + "invalidPortErrorText": "பிழை: தவறான போர்ட்.", + "invitationSentText": "அழைப்பு அனுப்பப்பட்டது.", + "invitationsSentText": "${COUNT} அழைப்புகள் அனுப்பப்பட்டன.", + "joinedPartyInstructionsText": "உங்கள் கட்சியில் ஒருவர் சேர்ந்துள்ளார்.\nவிளையாட்டைத் தொடங்க 'விளையாடு' என்பதற்குச் செல்லவும்.", + "keyboardText": "எழுத்துப்பலகை", + "kickIdlePlayersKickedText": "சும்மா இருப்பதற்காக ${NAME} ஐ வெளியேற்றப்பட்டது", + "kickIdlePlayersWarning1Text": "${NAME} சும்மா இருந்தால் ${COUNT} வினாடிகளில் வெளியேற்றப்படும்.", + "kickIdlePlayersWarning2Text": "(நீங்கள் இதை அமைப்புகள் -> மேம்பட்ட முறையில் அணைக்கலாம்)", + "leftGameText": "'${NAME}' வெளியேற பட்டது", + "leftPartyText": "${NAME} இன் பார்ட்டியை வெளியே விட்டு வந்தது.", + "noMusicFilesInFolderText": "போல்டரில் இசை இடம் இல்லை.", + "playerJoinedPartyText": "${NAME} பார்ட்டியில் சேர்ந்தார்!", + "playerLeftPartyText": "${NAME} பார்ட்டியில் விட்டு வெளியேறினார்.", + "rejectingInviteAlreadyInPartyText": "அழைப்பை நிராகரித்தல் (ஏற்கனவே ஒரு பார்ட்டியில் இருக்கிறீர்கள்).", + "serverRestartingText": "சர்வர் மறுதொடக்கம் செய்கிறது. தயவுசெய்து சிறிது நேரத்தில் மீண்டும் சேருங்கள்...", + "serverShuttingDownText": "சர்வர் முடக்கப்படுகிறது...", + "signInErrorText": "உள்நுழைவதில் பிழை.", + "signInNoConnectionText": "உள்நுழைய முடியவில்லை. (இணைய இணைப்பு இல்லையா?)", + "telnetAccessDeniedText": "பிழை: பயனர் டெல்நெட் அணுகலை வழங்கவில்லை.", + "timeOutText": "(${TIME} வினாடிகளில் வெளியேறும்)", + "touchScreenJoinWarningText": "நீங்கள் தொடுதிரையுடன் இணைந்துள்ளீர்கள்.\nஇது தவறு என்றால், அதனுடன் 'மெனு-> விளையாட்டிலிருந்து வெளியேறு' என்பதைத் தட்டவும்.", + "touchScreenText": "தொடு திரை", + "unableToResolveHostText": "பிழை: ஹோஸ்டை தீர்க்க முடியவில்லை.", + "unavailableNoConnectionText": "இது தற்போது கிடைக்கவில்லை (இணைய இணைப்பு இல்லையா?)", + "vrOrientationResetCardboardText": "VR நோக்குநிலையை மீட்டமைக்க இதைப் பயன்படுத்தவும்.\nவிளையாட்டை விளையாட உங்களுக்கு ஒரு வெளிப்புற கட்டுப்படுத்தி தேவை.", + "vrOrientationResetText": "VR நோக்குநிலை மீட்டமைப்பு.", + "willTimeOutText": "(சும்மா இருந்தால் நேரம் ஆகிவிடும்)" + }, + "jumpBoldText": "குதி", + "jumpText": "குதி", + "keepText": "வை", + "keepTheseSettingsText": "இந்த அமைப்புகளை வைத்திருக்கவா?", + "keyboardChangeInstructionsText": "விசைப்பலகைகளை மாற்ற இடத்தை இருமுறை அழுத்தவும்.", + "keyboardNoOthersAvailableText": "மற்ற விசைப்பலகைகள் இல்லை.", + "keyboardSwitchText": "விசைப்பலகையை \"${NAME}\" க்கு மாற்றுகிறது.", + "kickOccurredText": "${NAME} வெளியேற்றப்பட்டார்.", + "kickQuestionText": "${NAME} ஐ வெளியேற்றவா?", + "kickText": "அகற்று", + "kickVoteCantKickAdminsText": "நிர்வாகிகளை நீக்க முடியாது.", + "kickVoteCantKickSelfText": "உங்களை அகற்று முடியாது.", + "kickVoteFailedNotEnoughVotersText": "ஓட்டுக்கு போதுமான வீரர்கள் இல்லை.", + "kickVoteFailedText": "கிக்-ஓட்டு தோல்வியடைந்தது.", + "kickVoteStartedText": "${NAME} க்கு ஒரு கிக் வாக்களிப்பு தொடங்கப்பட்டது.", + "kickVoteText": "கிக் செய்ய வாக்களியுங்கள்", + "kickVotingDisabledText": "கிக் வாக்களிப்பு முடக்கப்பட்டுள்ளது.", + "kickWithChatText": "அரட்டையில் ஆம் என்று ${YES} என்றும் இல்லை என்பதற்கு ${NO} என்றும் தட்டச்சு செய்யவும்.", + "killsTallyText": "${COUNT} கொலைகள்", + "killsText": "கொலை", + "kioskWindow": { + "easyText": "சுலபம்", + "epicModeText": "காவிய முறை", + "fullMenuText": "முழு மெனு", + "hardText": "கடினமான", + "mediumText": "நடுத்தரமான", + "singlePlayerExamplesText": "ஒற்றை வீரர் / கூட்டுறவு உதாரணங்கள்", + "versusExamplesText": "வெர்சஸ் உதாரணங்கள்" + }, + "languageSetText": "மொழி இப்போது \"${LANGUAGE}\".", + "lapNumberText": "சுற்று ${CURRENT}/${TOTAL}", + "lastGamesText": "(கடைசி ${COUNT} விளையாட்டுகள்)", + "leaderboardsText": "லீடர்போர்டுகள்", + "league": { + "allTimeText": "எல்லா நேரமும்", + "currentSeasonText": "தற்போதைய பருவம் (${NUMBER})", + "leagueFullText": "${NAME} லீக்", + "leagueRankText": "லீக் ரேங்க்", + "leagueText": "லீக்", + "rankInLeagueText": "#${RANK}, ${NAME} லீக் ${SUFFIX}", + "seasonEndedDaysAgoText": "சீசன் ${NUMBER} நாட்களுக்கு முன்பு முடிந்தது.", + "seasonEndsDaysText": "சீசன் ${NUMBER} நாட்களில் முடிவடைகிறது.", + "seasonEndsHoursText": "சீசன் ${NUMBER} மணிநேரத்தில் முடிவடைகிறது.", + "seasonEndsMinutesText": "சீசன் ${NUMBER} நிமிடங்களில் முடிவடைகிறது.", + "seasonText": "சீசன் ${NUMBER}", + "tournamentLeagueText": "இந்த போட்டியில் நுழைய நீங்கள் ${NAME} லீக்கை அடைய வேண்டும்.", + "trophyCountsResetText": "அடுத்த சீசனில் கோப்பைகளின் எண்ணிக்கை மீட்டமைக்கப்படும்." + }, + "levelBestScoresText": "${LEVEL} இல் சிறந்த மதிப்பெண்கள்", + "levelBestTimesText": "${LEVEL} இல் சிறந்த நேரங்கள்", + "levelIsLockedText": "${LEVEL} லாக் செய்யப்பட்டது.", + "levelMustBeCompletedFirstText": "${LEVEL} முதலில் முடிக்கப்பட வேண்டும்.", + "levelText": "நிலை ${NUMBER}", + "levelUnlockedText": "நிலை திறக்கப்பட்டது!", + "livesBonusText": "லைவ் போனஸ்", + "loadingText": "ஏற்றுகிறது", + "loadingTryAgainText": "ஏற்றுகிறது; சிறிது நேரத்தில் மீண்டும் முயற்சி செய்...", + "macControllerSubsystemBothText": "இரண்டும் (பரிந்துரைக்கப்படவில்லை)", + "macControllerSubsystemClassicText": "தரமான", + "macControllerSubsystemDescriptionText": "(உங்கள் கட்டுப்படுத்திகள் வேலை செய்யவில்லை என்றால் இதை மாற்ற முயற்சிக்கவும்)", + "macControllerSubsystemMFiNoteText": "மேட்-ஃபார்-iOS/Mac கன்ட்ரோலர் கண்டறியப்பட்டது;\nநீங்கள் அமைப்புகள் -> கட்டுப்படுத்திகளில் இதைச் செயல்படுத்த விரும்பலாம்", + "macControllerSubsystemMFiText": "மேட்-ஃபார்-iOS/Mac", + "macControllerSubsystemTitleText": "கட்டுப்படுத்தி ஆதரவு", + "mainMenu": { + "creditsText": "வரவுகள்", + "demoMenuText": "டெமோ மெனு", + "endGameText": "விளையாட்டை வெளியேறு", + "exitGameText": "விளையாட்டை வெளியேறு", + "exitToMenuText": "மெனுவிலிருந்து வெளியேறவா?", + "howToPlayText": "எப்படி விளையாடுவது", + "justPlayerText": "(வெறும் ${NAME})", + "leaveGameText": "விளையாட்டில் வெளியேறு", + "leavePartyConfirmText": "உண்மையிலேயே பார்ட்டியை விட்டு வெளியேறலாமா?", + "leavePartyText": "பார்ட்டியிலிருந்து வெளியேறு", + "quitText": "வெளியேறு", + "resumeText": "தொடரு", + "settingsText": "அமைப்புகள்" + }, + "makeItSoText": "அவ்வாரே செய்", + "mapSelectGetMoreMapsText": "மேலும் வரைபடங்களைப் பெறுங்கள்...", + "mapSelectText": "தேர்ந்தெடுக்கவும்...", + "mapSelectTitleText": "${GAME} வரைபடங்கள்", + "mapText": "வரைபடம்", + "maxConnectionsText": "அதிகபட்ச இணைப்புகள்", + "maxPartySizeText": "அதிகபட்ச பார்ட்டி அளவு", + "maxPlayersText": "அதிகபட்ச வீரர்கள்", + "modeArcadeText": "ஆர்கேட் முறை", + "modeClassicText": "கிளாசிக் பயன்முறை", + "modeDemoText": "Demo Mode", + "mostValuablePlayerText": "மிகவும் மதிப்புமிக்க வீரர்", + "mostViolatedPlayerText": "மிகவும் மீறப்பட்ட வீரர்", + "mostViolentPlayerText": "மிகவும் வன்முறை வீரர்", + "moveText": "நகர்வு", + "multiKillText": "${COUNT}-கோலை!!!", + "multiPlayerCountText": "${COUNT} வீரர்கள்", + "mustInviteFriendsText": "குறிப்பு: நீங்கள் நண்பர்களை உள்ளே அழைக்க வேண்டும்\n\"${GATHER}\" பேனல் அல்லது இணைக்கவும்\nமல்டிபிளேயர் விளையாட கட்டுப்படுத்தி.", + "nameBetrayedText": "${NAME} ${VICTIM} ஐ துரோகம் செய்தார்.", + "nameDiedText": "${NAME} இறந்தார்.", + "nameKilledText": "${NAME} ${VICTIM} ஐக் கொன்றார்.", + "nameNotEmptyText": "பெயர் காலியாக இருக்க முடியாது!", + "nameScoresText": "${NAME} மதிப்பெண் பெற்றார்!", + "nameSuicideKidFriendlyText": "${NAME} தற்செயலாக இறந்தார்.", + "nameSuicideText": "${NAME} தற்கொலை செய்து கொண்டார்.", + "nameText": "பெயர்", + "nativeText": "பூர்வீகம்", + "newPersonalBestText": "புதிய தனிப்பட்ட சிறந்த!", + "newTestBuildAvailableText": "ஒரு புதிய சோதனை உருவாக்கம் கிடைக்கிறது! (${VERSION} உருவாக்க ${BUILD}).\n${ADDRESS} இல் பெறுங்கள்.", + "newText": "புதிய", + "newVersionAvailableText": "${APP_NAME} இன் புதிய பதிப்பு கிடைக்கிறது! (${VERSION})", + "nextAchievementsText": "அடுத்த சாதனைகள்:", + "nextLevelText": "அடுத்த நிலை", + "noAchievementsRemainingText": "- இல்லை", + "noContinuesText": "(தொடரும் இல்லை)", + "noExternalStorageErrorText": "இந்தச் சாதனத்தில் வெளிப்புறச் சேமிப்பு இல்லை", + "noGameCircleText": "பிழை: GameCircle இல் உள்நுழையவில்லை", + "noScoresYetText": "இன்னும் மதிப்பெண்கள் இல்லை.", + "noThanksText": "இல்லை நன்றி", + "noTournamentsInTestBuildText": "எச்சரிக்கை: இந்த சோதனை உருவாக்கத்தில் இருந்து போட்டியின் மதிப்பெண்கள் புறக்கணிக்கப்படும்.", + "noValidMapsErrorText": "இந்த விளையாட்டு வகைக்கு சரியான வரைபடங்கள் இல்லை.", + "notEnoughPlayersRemainingText": "போதுமான வீரர்கள் மீதமில்லை; வெளியேறி ஒரு புதிய விளையாட்டைத் தொடங்கவும்.", + "notEnoughPlayersText": "இந்த விளையாட்டைத் தொடங்க குறைந்தபட்சம் ${COUNT} வீரர்கள் தேவை!", + "notNowText": "இப்போது இல்லை", + "notSignedInErrorText": "இதைச் செய்ய நீங்கள் உள்நுழைய வேண்டும்.", + "notSignedInGooglePlayErrorText": "இதைச் செய்ய நீங்கள் Google Play இல் உள்நுழைய வேண்டும்.", + "notSignedInText": "உள்நுழையவில்லை", + "nothingIsSelectedErrorText": "எதுவும் தேர்ந்தெடுக்கப்படவில்லை!", + "numberText": "#${NUMBER}", + "offText": "ஆஃப்", + "okText": "சரி", + "onText": "வை", + "oneMomentText": "ஒரு நிமிடம்...", + "onslaughtRespawnText": "${PLAYER} அலை ${WAVE} இல் வருவார்", + "orText": "${A} அல்லது ${B}", + "otherText": "மற்ற...", + "outOfText": "(${ALL} இல்#${RANK})", + "ownFlagAtYourBaseWarning": "உங்கள் சொந்த கொடி இருக்க வேண்டும்\nஅடிப்பதற்கு உங்கள் அடித்தளத்தில்!", + "packageModsEnabledErrorText": "உள்ளூர்-பேக்கேஜ்-மோட்கள் இயக்கப்பட்டிருக்கும் போது நெட்வொர்க்-ப்ளே அனுமதிக்கப்படாது (அமைப்புகள்-> மேம்பட்டதைப் பார்க்கவும்)", + "partyWindow": { + "chatMessageText": "அரட்டை செய்தி", + "emptyText": "உங்கள் பார்ட்டி காலியாக உள்ளது", + "hostText": "(ஹோஸ்ட்)", + "sendText": "அனுப்பு", + "titleText": "உங்கள் பார்ட்டி" + }, + "pausedByHostText": "(தொகுப்பாளரால் இடைநிறுத்தப்பட்டது)", + "perfectWaveText": "சரியான அலை!", + "pickUpText": "எடு", + "playModes": { + "coopText": "கோ-ஓப்", + "freeForAllText": "பிரீ-போர்-ஆல்", + "multiTeamText": "பல-குழு", + "singlePlayerCoopText": "ஒற்றை வீரர் / கோ-ஓப்", + "teamsText": "குழுக்கள்" + }, + "playText": "விளையாடு", + "playWindow": { + "oneToFourPlayersText": "1-4 வீரர்கள்", + "titleText": "Play", + "twoToEightPlayersText": "2-8 வீரர்கள்" + }, + "playerCountAbbreviatedText": "${COUNT}ப", + "playerDelayedJoinText": "${PLAYER} அடுத்த சுற்றின் தொடக்கத்தில் நுழைவார்.", + "playerInfoText": "Player Info", + "playerLeftText": "${PLAYER} விளையாட்டை விட்டு வெளியேறினார்.", + "playerLimitReachedText": "பிளேயர் வரம்பு ${COUNT} ஐ எட்டியது; இணைந்தவர்கள் அனுமதிக்கப்படவில்லை.", + "playerProfilesWindow": { + "cantDeleteAccountProfileText": "உங்கள் கணக்கு சுயவிவரத்தை நீக்க முடியாது.", + "deleteButtonText": "அழி\nசுயவிவரம்", + "deleteConfirmText": "'${PROFILE}' ஐ நீக்கவா?", + "editButtonText": "தொகு\nசுயவிவரம்", + "explanationText": "(இந்த கணக்கிற்கான தனிப்பயன் பிளேயர் பெயர்கள் மற்றும் தோற்றங்கள்)", + "newButtonText": "புதிய\nசுயவிவரம்", + "titleText": "பிளேயர் சுயவிவரங்கள்" + }, + "playerText": "பிளேயர்", + "playlistNoValidGamesErrorText": "இந்த பிளேலிஸ்ட்டில் சரியான திறக்கப்பட்ட கேம்கள் இல்லை.", + "playlistNotFoundText": "பிளேலிஸ்ட் கிடைக்கவில்லை", + "playlistText": "பிளேலிஸ்ட்", + "playlistsText": "பிளேலிஸ்ட்கள்", + "pleaseRateText": "நீங்கள் ${APP_NAME} ஐ அனுபவிக்கிறீர்கள் என்றால், தயவுசெய்து எடுப்பதைக் கவனியுங்கள்\nதருணம் மற்றும் மதிப்பிடுதல் அல்லது விமர்சனம் எழுதுதல். இது வழங்குகிறது\nபயனுள்ள கருத்து மற்றும் எதிர்கால வளர்ச்சிக்கு உதவுகிறது.\n\nநன்றி!\n-எரிக்", + "pleaseWaitText": "தயவுசெய்து காத்திருங்கள்...", + "pluginsDetectedText": "புதிய செருகுநிரல் (கள்) கண்டறியப்பட்டது. அமைப்புகளில் அவற்றை இயக்கவும்/கட்டமைக்கவும்.", + "pluginsText": "செருகுநிரல்கள்", + "practiceText": "பயிற்சி", + "pressAnyButtonPlayAgainText": "மீண்டும் விளையாட எந்த பட்டனையும் அழுத்தவும்...", + "pressAnyButtonText": "தொடர எந்த பட்டனையும் அழுத்தவும்...", + "pressAnyButtonToJoinText": "சேர எந்த பட்டனையும் அழுத்தவும்...", + "pressAnyKeyButtonPlayAgainText": "மீண்டும் இயக்க எந்த விசையையும்/பட்டனையும் அழுத்தவும்...", + "pressAnyKeyButtonText": "தொடர எந்த விசையையும்/பட்டனையும் அழுத்தவும்...", + "pressAnyKeyText": "எந்த விசையையும் அழுத்தவும்...", + "pressJumpToFlyText": "** பறக்க மீண்டும் குதி பட்டனை அழுத்தவும் **", + "pressPunchToJoinText": "சேர, குத்து அழுத்தவும்...", + "pressToOverrideCharacterText": "உங்கள் குணசாலி மீற ${BUTTONS} ஐ அழுத்தவும்", + "pressToSelectProfileText": "பிளேயரைத் தேர்ந்தெடுக்க ${BUTTONS} ஐ அழுத்தவும்", + "pressToSelectTeamText": "ஒரு குழுவைத் தேர்ந்தெடுக்க ${BUTTONS} ஐ அழுத்தவும்", + "promoCodeWindow": { + "codeText": "குறியீடு", + "enterText": "நுழை" + }, + "promoSubmitErrorText": "குறியீட்டைச் சமர்ப்பிப்பதில் பிழை; உங்கள் இணைய இணைப்பைச் சரிபார்க்கவும்", + "ps3ControllersWindow": { + "macInstructionsText": "உங்கள் PS3 இன் பின்புறத்தில் உள்ள சக்தியை அணைக்கவும், உறுதி செய்யவும்\nஉங்கள் மேக்கில் புளூடூத் இயக்கப்பட்டது, பின்னர் உங்கள் கட்டுப்படுத்தியை இணைக்கவும்\nஇரண்டையும் இணைக்க யூ.எஸ்.பி கேபிள் வழியாக உங்கள் மேக்கில். அப்போதிருந்து, நீங்கள்\nஉங்கள் மேக் உடன் இணைக்க கட்டுப்படுத்தியின் முகப்பு பொத்தானைப் பயன்படுத்தலாம்\nகம்பி (USB) அல்லது வயர்லெஸ் (ப்ளூடூத்) முறையில்.\n\nசில மேக்ஸில் நீங்கள் இணைக்கும் போது ஒரு கடவுக்குறியீடு கேட்கப்படும்.\nஇது நடந்தால், உதவிக்கு பின்வரும் டுடோரியல் அல்லது கூகிளைப் பார்க்கவும்.\n\n\n\n\nவயர்லெஸ் இணைக்கப்பட்ட PS3 கட்டுப்படுத்திகள் சாதனத்தில் காட்டப்பட வேண்டும்\nகணினி விருப்பத்தேர்வுகள்-> ப்ளூடூத். நீங்கள் அவற்றை அகற்ற வேண்டியிருக்கலாம்\nஉங்கள் PS3 உடன் அவற்றை மீண்டும் பயன்படுத்த விரும்பும் போது அந்தப் பட்டியலில் இருந்து.\n\nப்ளூடூத் இல்லாத போது அவற்றைத் துண்டிக்க வேண்டும்\nபயன்பாடு அல்லது அவற்றின் பேட்டரிகள் தொடர்ந்து வெளியேறும்.\n\nபுளூடூத் 7 இணைக்கப்பட்ட சாதனங்களை கையாள வேண்டும்,\nஇருந்தாலும் உங்கள் மைலேஜ் மாறுபடலாம்.", + "macInstructionsTextScale": 0.74, + "ouyaInstructionsText": "உங்கள் OUYA உடன் PS3 கட்டுப்படுத்தியைப் பயன்படுத்த, அதை USB கேபிள் மூலம் இணைக்கவும்\nஒரு முறை அதை இணைக்க. இதைச் செய்வது உங்கள் மற்ற கட்டுப்படுத்திகளைத் துண்டிக்கக்கூடும்\nநீங்கள் உங்கள் OUYA ஐ மறுதொடக்கம் செய்து USB கேபிளை அகற்ற வேண்டும்.\n\nஅப்போதிலிருந்து நீங்கள் கட்டுப்படுத்தியின் வீட்டு பொத்தானைப் பயன்படுத்த முடியும்\nஅதை கம்பியில்லாமல் இணைக்கவும். நீங்கள் விளையாடி முடித்ததும், வீட்டு பொத்தானை அழுத்திப் பிடிக்கவும்\nகட்டுப்படுத்தியை அணைக்க 10 விநாடிகள்; இல்லையெனில் அது தொடர்ந்து இருக்கலாம்\nமற்றும் கழிவு பேட்டரிகள்.", + "ouyaInstructionsTextScale": 0.74, + "pairingTutorialText": "இணைத்தல் பயிற்சி வீடியோ", + "titleText": "${APP_NAME} உடன் PS3 கட்டுப்படுத்திகளைப் பயன்படுத்துதல்:" + }, + "punchBoldText": "குத்து", + "punchText": "குத்து", + "purchaseForText": "${PRICE} க்கு வாங்கு", + "purchaseGameText": "வாங்கு விளையாட்டு", + "purchasingText": "வாங்குகிறது...", + "quitGameText": "${APP_NAME} ஐ விட்டு வெளியேறவா?", + "quittingIn5SecondsText": "5 வினாடிகளில் வெளியேறும்...", + "randomPlayerNamesText": "DEFAULT_NAMES", + "randomText": "சீரற்ற", + "rankText": "ரேங்க்", + "ratingText": "மதிப்பீடு", + "reachWave2Text": "ரேங்க் செய்ய அலை 2 ஐ அடையுங்கள்.", + "readyText": "தயார்", + "recentText": "சமீபத்திய", + "remoteAppInfoShortText": "குடும்பம் & நண்பர்களுடன் விளையாடும்போது ${APP_NAME} மிகவும் வேடிக்கையாக உள்ளது.\nஒன்று அல்லது அதற்கு மேற்பட்ட வன்பொருள் கட்டுப்படுத்திகளை இணைக்கவும் அல்லது நிறுவவும்\nதொலைபேசிகள் அல்லது டேப்லெட்களில் அவற்றைப் பயன்படுத்த ${REMOTE_APP_NAME} பயன்பாடு\nகட்டுப்படுத்திகளாக.", + "remote_app": { + "app_name": "BombSquad ரிமோட்", + "app_name_short": "BSரிமோட்", + "button_position": "பட்டன் நிலை", + "button_size": "பட்டன் அளவு", + "cant_resolve_host": "புரவலரைத் தீர்க்க முடியவில்லை.", + "capturing": "பிடிக்கிறது...", + "connected": "இணைக்கப்பட்டது.", + "description": "உங்கள் தொலைபேசி அல்லது டேப்லெட்டை BombSquad உடன் கட்டுப்படுத்தியாகப் பயன்படுத்தவும்.\nஒரே டிவி அல்லது டேப்லெட்டில் உள்ள காவிய உள்ளூர் மல்டிபிளேயர் பைத்தியக்காரத்தனத்திற்கு 8 சாதனங்கள் வரை ஒரே நேரத்தில் இணைக்க முடியும்.", + "disconnected": "சேவையகத்தால் துண்டிக்கப்பட்டது.", + "dpad_fixed": "சரி செய்யப்பட்டது", + "dpad_floating": "மிதக்கும்", + "dpad_position": "D-Pad நிலை", + "dpad_size": "D-Pad அளவு", + "dpad_type": "D-Pad வகை", + "enter_an_address": "ஒரு முகவரியை உள்ளிடவும்", + "game_full": "விளையாட்டு நிரம்பியுள்ளது அல்லது இணைப்புகளை ஏற்கவில்லை.", + "game_shut_down": "விளையாட்டு மூடப்பட்டது.", + "hardware_buttons": "வன்பொருள் பட்டன்கள்", + "join_by_address": "முகவரி மூலம் சேருங்கள்...", + "lag": "பின்னடைவு: ${SECONDS} வினாடிகள்", + "reset": "இயல்புநிலைக்கு மீட்டமைக்கவும்", + "run1": "ரன் 1", + "run2": "ரன் 2", + "searching": "BombSquad கேம்களைத் தேடுகிறது...", + "searching_caption": "விளையாட்டில் சேர அதன் பெயரைத் தட்டவும்.\nவிளையாட்டின் அதே வைஃபை நெட்வொர்க்கில் நீங்கள் இருப்பதை உறுதிப்படுத்திக் கொள்ளுங்கள்.", + "start": "தொடங்கு", + "version_mismatch": "பதிப்பு பொருந்தவில்லை.\nBombSquad மற்றும் BombSquad ரிமோட்டை உறுதி செய்யவும்\nசமீபத்திய பதிப்புகள் மற்றும் மீண்டும் முயற்சிக்கவும்." + }, + "removeInGameAdsText": "விளையாட்டு விளம்பரங்களை அகற்ற ஸ்டோரில் \"${PRO}\" ஐத் வாங்கவும்.", + "renameText": "மறுபெயரிடு", + "replayEndText": "ரீப்ளேயிலிருந்து வெளியேறு", + "replayNameDefaultText": "கடைசி கேம் ரீப்ளே", + "replayReadErrorText": "ரீப்ளே கோப்பைப் படிப்பதில் பிழை.", + "replayRenameWarningText": "நீங்கள் ஒரு விளையாட்டை வைத்திருக்க விரும்பினால் \"${REPLAY}\" என்று மறுபெயரிடுங்கள்; இல்லையெனில் அது மேலெழுதப்படும்.", + "replayVersionErrorText": "மன்னிக்கவும், இந்த ரீப்ளே வேறு வகையில் செய்யப்பட்டது\nவிளையாட்டின் பதிப்பு மற்றும் பயன்படுத்த முடியாது.", + "replayWatchText": "ரீப்ளே பார்", + "replayWriteErrorText": "ரிப்ளே கோப்பை எழுதுவதில் பிழை.", + "replaysText": "ரீப்ளேகள்", + "reportPlayerExplanationText": "ஏமாற்றுதல், பொருத்தமற்ற மொழி அல்லது பிற கெட்ட நடத்தை குறித்து புகாரளிக்க இந்த மின்னஞ்சலைப் பயன்படுத்தவும்.\nதயவுசெய்து கீழே விவரிக்கவும்:", + "reportThisPlayerCheatingText": "ஏமாற்றுதல்", + "reportThisPlayerLanguageText": "பொருத்தமற்ற மொழி", + "reportThisPlayerReasonText": "நீங்கள் என்ன தெரிவிக்க விரும்புகிறீர்கள்?", + "reportThisPlayerText": "இந்த பிளேயரைப் புகாரளிக்கவும்", + "requestingText": "கோருகிறது...", + "restartText": "மறுதொடக்கம்", + "retryText": "மீண்டும் முயற்சிக்கவும்", + "revertText": "பின்செல்", + "runText": "ஓடு", + "saveText": "சேமி", + "scanScriptsErrorText": "ஸ்கிரிப்ட்களை ஸ்கேன் செய்வதில் பிழை (கள்); விவரங்களுக்கு பதிவைப் பார்க்கவும்.", + "scoreChallengesText": "ஸ்கோர் சவால்கள்", + "scoreListUnavailableText": "மதிப்பெண் பட்டியல் கிடைக்கவில்லை.", + "scoreText": "மதிப்பெண் பெரு", + "scoreUnits": { + "millisecondsText": "மில்லிவிநாடிகள்", + "pointsText": "புள்ளிகள்", + "secondsText": "வினாடிகள்" + }, + "scoreWasText": "(${COUNT} இருந்தது)", + "selectText": "தேர்ந்தெடு", + "seriesWinLine1PlayerText": "வெற்றி", + "seriesWinLine1TeamText": "வெற்றி", + "seriesWinLine1Text": "வெற்றி", + "seriesWinLine2Text": "தொடர்!", + "settingsWindow": { + "accountText": "கணக்கு", + "advancedText": "மேம்பட்ட அமைப்புகள்", + "audioText": "ஆடியோ", + "controllersText": "கட்டுப்பாட்டாளர்கள்", + "graphicsText": "கிராபிக்ஸ்", + "playerProfilesMovedText": "குறிப்பு: பிளேயர் சுயவிவரங்கள் பிரதான மெனுவில் உள்ள கணக்கு சாளரத்திற்கு நகர்த்தப்பட்டுள்ளன.", + "titleText": "அமைப்புகள்" + }, + "settingsWindowAdvanced": { + "alwaysUseInternalKeyboardDescriptionText": "(உரை திருத்துவதற்கான ஒரு எளிய, கட்டுப்படுத்தி-நட்பு திரையில் விசைப்பலகை)", + "alwaysUseInternalKeyboardText": "எப்போதும் உள் விசைப்பலகையைப் பயன்படுத்தவும்", + "benchmarksText": "அளவுகோல்கள் மற்றும் மன அழுத்த சோதனைகள்", + "disableCameraGyroscopeMotionText": "கேமரா கைரோஸ்கோப் இயக்கத்தை முடக்கவும்", + "disableCameraShakeText": "கேமரா குலுக்கை முடக்கு", + "disableThisNotice": "(மேம்பட்ட அமைப்புகளில் இந்த அறிவிப்பை முடக்கலாம்)", + "enablePackageModsDescriptionText": "(கூடுதல் மோடிங் திறன்களை செயல்படுத்துகிறது ஆனால் நெட்-ப்ளேவை முடக்குகிறது)", + "enablePackageModsText": "உள்ளூர் தொகுப்பு முறைகளை இயக்கு", + "enterPromoCodeText": "குறியீட்டை உள்ளிடவும்", + "forTestingText": "குறிப்பு: இந்த மதிப்புகள் சோதனைக்கு மட்டுமே மற்றும் பயன்பாடு வெளியேறும் போது", + "helpTranslateText": "${APP_NAME} இன் ஆங்கிலம் அல்லாத மொழிபெயர்ப்புகள் ஒரு சமூகம்\nஆதரவான முயற்சி. நீங்கள் பங்களிக்க அல்லது திருத்த விரும்பினால்\nஒரு மொழிபெயர்ப்பு, கீழே உள்ள இணைப்பைப் பின்தொடரவும். முன்கூட்டியே நன்றி!", + "kickIdlePlayersText": "செயலற்ற வீரர்களை வெளியேற்றவும்", + "kidFriendlyModeText": "குழந்தை நட்பு முறை (குறைந்த வன்முறை போன்றவை)", + "languageText": "மொழி", + "moddingGuideText": "மோடிங் வழிகாட்டி", + "mustRestartText": "இது நடைமுறைக்கு வர நீங்கள் விளையாட்டை மறுதொடக்கம் செய்ய வேண்டும்.", + "netTestingText": "நெட்வொர்க் சோதனை", + "resetText": "மீட்டு", + "showBombTrajectoriesText": "வெடிகுண்டு பாதைகளைக் காட்டு", + "showPlayerNamesText": "பிளேயர் பெயர்களைக் காட்டு", + "showUserModsText": "மோட்ஸ் கோப்புறையைக் காட்டு", + "titleText": "மேம்பட்ட அமைப்புகள்", + "translationEditorButtonText": "${APP_NAME} மொழிபெயர்ப்பு எடிட்டர்", + "translationFetchErrorText": "மொழிபெயர்ப்பு நிலை கிடைக்கவில்லை", + "translationFetchingStatusText": "மொழிபெயர்ப்பு நிலையை சரிபார்க்கிறது...", + "translationInformMe": "எனது மொழிக்கு புதுப்பிப்புகள் தேவைப்படும்போது எனக்குத் தெரியப்படுத்துங்கள்", + "translationNoUpdateNeededText": "தற்போதைய மொழி புதுப்பித்த நிலையில் உள்ளது; வூஹூ!", + "translationUpdateNeededText": "** தற்போதைய மொழிக்கு புதுப்பிப்புகள் தேவை !! **", + "vrTestingText": "VR சோதனை" + }, + "shareText": "பகிர்", + "sharingText": "பகிர்வு...", + "showText": "காட்டு", + "signInForPromoCodeText": "குறியீடுகள் நடைமுறைக்கு வர நீங்கள் ஒரு கணக்கில் உள்நுழைய வேண்டும்.", + "signInWithGameCenterText": "Game Center கணக்கைப் பயன்படுத்த,\nGame Center ஆப் மூலம் உள்நுழையவும்.", + "singleGamePlaylistNameText": "வெறும் ${GAME}", + "singlePlayerCountText": "1 வீரர்", + "soloNameFilterText": "தனி ${NAME}", + "soundtrackTypeNames": { + "CharSelect": "குணம் தேர்ந்தெடுத்தல்", + "Chosen One": "சொசன் ஒன்று", + "Epic": "காவிய முறை விளையாட்டுகள்", + "Epic Race": "காவிய ஓட்டப்பந்தயம்", + "FlagCatcher": "கொடியை கைப்பற்றவும்", + "Flying": "ஹாப்பி தோட்ஹ்ட்ஸ்", + "Football": "கால்பந்து", + "ForwardMarch": "தாக்குதல்", + "GrandRomp": "அடைப்படுத்தல்", + "Hockey": "ஹாக்கி", + "Keep Away": "ஒதுக்கி வைக்கவும்", + "Marching": "சுற்றி ஓடு", + "Menu": "முதன்மை பட்டியல்", + "Onslaught": "தாக்குதல்", + "Race": "பந்தயம்", + "Scary": "மலையின் அரசன்", + "Scores": "ஸ்கோர் ஸ்கிரீன்", + "Survival": "நீக்குதல்", + "ToTheDeath": "மரண விளையாட்டு", + "Victory": "இறுதி மதிப்பெண் திரை" + }, + "spaceKeyText": "இடஎல்லை", + "statsText": "புள்ளிவிவரங்கள்", + "storagePermissionAccessText": "இதற்கு சேமிப்பு அணுகல் தேவை", + "store": { + "alreadyOwnText": "நீங்கள் ஏற்கனவே ${NAME} ஐ வைத்திருக்கிறீர்கள்!", + "bombSquadProNameText": "${APP_NAME} ப்ரோ", + "bombSquadProNewDescriptionText": "• விளையாட்டு விளம்பரங்கள் மற்றும் நாக் திரைகளை நீக்குகிறது\n• மேலும் விளையாட்டு அமைப்புகளைத் திறக்கிறது\n• மேலும் உள்ளடக்கியது:", + "buyText": "வாங்கு", + "charactersText": "குணங்கள்", + "comingSoonText": "விரைவில் வரும்...", + "extrasText": "கூடுதல்", + "freeBombSquadProText": "BombSquad இப்போது இலவசம், ஆனால் நீங்கள் முதலில் அதை வாங்கியதிலிருந்து நீங்கள்\nBombSquad Pro மேம்படுத்தல் மற்றும் ${COUNT} டிக்கெட்டுகளை நன்றியுடன் பெறுதல்.\nபுதிய அம்சங்களை அனுபவிக்கவும், உங்கள் ஆதரவுக்கு நன்றி!\n-எரிக்", + "holidaySpecialText": "விடுமுறை சிறப்பு", + "howToSwitchCharactersText": "(குணங்களை ஒதுக்க & தனிப்பயனாக்க \"${SETTINGS} -> ${PLAYER_PROFILES}\" க்குச் செல்லவும்)", + "howToUseIconsText": "(உலகளாவிய பிளேயர் சுயவிவரங்களை உருவாக்கவும் (கணக்கு சாளரத்தில்) இவற்றைப் பயன்படுத்தவும்)", + "howToUseMapsText": "(இந்த வரைபடங்களை உங்கள் சொந்த அணிகளில் பயன்படுத்தவும்/இலவசமாக அனைவருக்கும் பிளேலிஸ்ட்கள்)", + "iconsText": "சின்னங்கள்", + "loadErrorText": "பக்கத்தை ஏற்ற முடியவில்லை.\nஉங்கள் இணைய இணைப்பைச் சரிபார்க்கவும்.", + "loadingText": "ஏற்றுகிறது", + "mapsText": "Maps", + "miniGamesText": "மினிகேம்ஸ்", + "oneTimeOnlyText": "(ஒரு முறை மட்டும்)", + "purchaseAlreadyInProgressText": "இந்த பொருளை வாங்குவது ஏற்கனவே நடந்து கொண்டிருக்கிறது.", + "purchaseConfirmText": "${ITEM} வாங்கவா?", + "purchaseNotValidError": "கொள்முதல் செல்லுபடியாகாது.\nஇது பிழை என்றால் ${EMAIL} ஐ தொடர்பு கொள்ளவும்.", + "purchaseText": "வாங்கு", + "saleBundleText": "மூட்டை விற்பனை!", + "saleExclaimText": "விற்பனை!", + "salePercentText": "(${PERCENT}% தள்ளுபடி)", + "saleText": "விற்பனை", + "searchText": "தேடு", + "teamsFreeForAllGamesText": "அணிகள் / பிரீ-போர்-ஆல் விளையாட்டுகள்", + "totalWorthText": "*** ${TOTAL_WORTH} மதிப்பு! ***", + "upgradeQuestionText": "மேம்படுத்தல்?", + "winterSpecialText": "குளிர்கால சிறப்பு", + "youOwnThisText": "- இது உங்களுக்கு சொந்தமானது -" + }, + "storeDescriptionText": "8 பிளேயர் பார்டி விளையாட்டு பைத்தியம்!\n\nபிடிப்பு-கொடி, வெடிகுண்டு-ஹாக்கி மற்றும் காவிய-மெதுவான-இயக்கம்-இறப்பு-போட்டி போன்ற வெடிக்கும் மினி-கேம்களின் போட்டியில் உங்கள் நண்பர்களை (அல்லது கணினியை) வெடிக்கச் செய்யுங்கள்!\n\nஎளிய கட்டுப்பாடுகள் மற்றும் விரிவான கட்டுப்பாட்டாளர் ஆதரவு 8 பேர் வரை செயலில் இறங்குவதை எளிதாக்குகிறது; இலவச 'பாம்ப்ஸ்க்வாட் ரிமோட்' ஆப் மூலம் உங்கள் மொபைல் சாதனங்களை கட்டுப்படுத்திகளாகவும் பயன்படுத்தலாம்!\n\nவெடிகுண்டுகள்!\n\nமேலும் தகவலுக்கு www.froemling.net/bombsquad ஐ பார்க்கவும்.", + "storeDescriptions": { + "blowUpYourFriendsText": "உங்கள் நண்பர்களை வெடிக்கச் செய்யுங்கள்.", + "competeInMiniGamesText": "பந்தயத்தில் இருந்து பறக்கும் வரை மினி-கேம்களில் போட்டியிடவும்.", + "customize2Text": "குணங்கள், மினி-கேம்கள் மற்றும் ஒலிப்பதிவையும் தனிப்பயனாக்கவும்.", + "customizeText": "குணங்களைத் தனிப்பயனாக்கி உங்கள் சொந்த மினி-கேம் பிளேலிஸ்ட்களை உருவாக்கவும்.", + "sportsMoreFunText": "வெடிபொருட்களுடன் விளையாட்டு மிகவும் வேடிக்கையாக உள்ளது.", + "teamUpAgainstComputerText": "கணினிக்கு எதிராக அணிசேருங்கள்." + }, + "storeText": "ஸ்டோர்", + "submitText": "சமர்ப்பிக்கவும்", + "submittingPromoCodeText": "குறியீட்டைச் சமர்ப்பிக்கிறது...", + "teamNamesColorText": "அணியின் பெயர்கள்/நிறங்கள்...", + "telnetAccessGrantedText": "டெல்நெட் அணுகல் இயக்கப்பட்டது.", + "telnetAccessText": "டெல்நெட் அணுகல் கண்டறியப்பட்டது; அனுமதிக்கவா?", + "testBuildErrorText": "இந்த சோதனை உருவாக்கம் இனி செயலில் இல்லை; தயவுசெய்து புதிய பதிப்பைச் சரிபார்க்கவும்.", + "testBuildText": "சோதனை கட்டமைப்பு", + "testBuildValidateErrorText": "சோதனை கட்டமைப்பை சரிபார்க்க முடியவில்லை. (நெட் இணைப்பு இல்லையா?)", + "testBuildValidatedText": "சோதனை கட்டமைப்பு சரிபார்க்கப்பட்டது; மகிழுங்கள்!", + "thankYouText": "உங்கள் ஆதரவுக்கு நன்றி! ஆட்டத்தை ரசி!!", + "threeKillText": "மூன்று கொலை!!", + "timeBonusText": "நேரம் போனஸ்", + "timeElapsedText": "நேரம் கடந்துவிட்டது", + "timeExpiredText": "நேரம் காலாவதியானது", + "timeSuffixDaysText": "${COUNT}d", + "timeSuffixHoursText": "${COUNT}h", + "timeSuffixMinutesText": "${COUNT}m", + "timeSuffixSecondsText": "${COUNT}s", + "tipText": "உதவிக்குறிப்பு", + "titleText": "பாம்ஸ்குவாட்", + "titleVRText": "பாம்ஸ்குவாட் VR", + "topFriendsText": "சிறந்த நண்பர்கள்", + "tournamentCheckingStateText": "போட்டி நிலையை சரிபார்க்கிறது; தயவுசெய்து காத்திருங்கள்...", + "tournamentEndedText": "இந்த போட்டி முடிந்தது. புதியது விரைவில் தொடங்கும்.", + "tournamentEntryText": "போட்டியின் நுழைவு", + "tournamentResultsRecentText": "சமீபத்திய போட்டி முடிவுகள்", + "tournamentStandingsText": "போட்டி நிலைகள்", + "tournamentText": "போட்டி", + "tournamentTimeExpiredText": "போட்டி நேரம் காலாவதியானது", + "tournamentsText": "Tournaments", + "translations": { + "characterNames": { + "Agent Johnson": "ஏஜெண்ட் ஜான்சன்", + "B-9000": "B-9000", + "Bernard": "பர்னார்ட்", + "Bones": "போன்ஸ்", + "Butch": "பட்ச்", + "Easter Bunny": "ஈஸ்டர் பண்ணி", + "Flopsy": "ப்லாப்சி", + "Frosty": "பிரோஸ்ட்டி", + "Gretel": "கிரெடெல்", + "Grumbledorf": "கிறம்பில் டோரப்", + "Jack Morgan": "ஜாக் மோர்கன்", + "Kronk": "க்றோங்க்", + "Lee": "லி", + "Lucky": "லக்கி", + "Mel": "மெல்", + "Middle-Man": "மிடில்-மண்", + "Minimus": "மினிமஸ்", + "Pascal": "பாஸ்கல்", + "Pixel": "பிக்ஸெல்", + "Sammy Slam": "சம்மி ஸ்லம்", + "Santa Claus": "சாண்டா கிளாஸ்", + "Snake Shadow": "பாம்பு நிழல்", + "Spaz": "ஸ்பாஸ்", + "Taobao Mascot": "தாவோபா மாஸ்காட்", + "Todd McBurton": "டாட் மக்பூர்டன்", + "Zoe": "ஜோய்", + "Zola": "ஜோலா" + }, + "coopLevelNames": { + "${GAME} Training": "${GAME} பயிற்சி", + "Infinite ${GAME}": "எல்லையற்ற ${GAME}", + "Infinite Onslaught": "எல்லையற்ற தாக்குதல்", + "Infinite Runaround": "எல்லையற்ற சுற்றி ஓடு", + "Onslaught Training": "தாக்குதல் பயிற்சி", + "Pro ${GAME}": "ப்ரோ ${GAME}", + "Pro Football": "ப்ரோ கால்பந்து", + "Pro Onslaught": "ப்ரோ தாக்குதல்", + "Pro Runaround": "ப்ரோ சுற்றி ஓடு", + "Rookie ${GAME}": "ரூகீ ${GAME}", + "Rookie Football": "ரூகீ கால்பந்து", + "Rookie Onslaught": "ரூகீ தாக்குதல்", + "The Last Stand": "தி கடைசி ஸ்டாண்ட்", + "Uber ${GAME}": "உபர் ${GAME}", + "Uber Football": "உபர் கால்பந்து", + "Uber Onslaught": "உபர் தாக்குதல்", + "Uber Runaround": "உபர் சுற்றி ஓடு" + }, + "gameDescriptions": { + "Be the chosen one for a length of time to win.\nKill the chosen one to become it.": "வெற்றிபெற நீண்ட நேரம் தேர்ந்தெடுக்கப்பட்டவராக இருங்கள்.\nதேர்ந்தெடுக்கப்பட்டவரை அதைக் கொல்லுங்கள்.", + "Bomb as many targets as you can.": "உங்களால் முடிந்தவரை பல இலக்குகளை வெடிக்கவும்.", + "Carry the flag for ${ARG1} seconds.": "கொடியை ${ARG1} வினாடிகளுக்கு எடுத்துச் செல்லவும்", + "Carry the flag for a set length of time.": "ஒரு குறிப்பிட்ட நேரத்திற்கு கொடியை எடுத்துச் செல்லவும்.", + "Crush ${ARG1} of your enemies.": "உங்கள் எதிரிகளின் ${ARG1} ஐ நசுக்கவும்.", + "Defeat all enemies.": "அனைத்து எதிரிகளையும் தோற்கடிக்கவும்.", + "Dodge the falling bombs.": "விழும் குண்டுகளைத் தள்ளிவிடுங்கள்.", + "Final glorious epic slow motion battle to the death.": "இறுதி புகழ்பெற்ற காவிய மெதுவான நகர்வு போர்.", + "Gather eggs!": "முட்டைகளை சேகரிக்கவும்!", + "Get the flag to the enemy end zone.": "எதிரியின் இறுதி மண்டலத்திற்கு கொடியைப் பெறுங்கள்.", + "How fast can you defeat the ninjas?": "நீங்கள் நிஞ்ஜாக்களை எவ்வளவு விரைவாக தோற்கடிக்க முடியும்?", + "Kill a set number of enemies to win.": "வெற்றிபெற ஒரு குறிப்பிட்ட எண்ணிக்கையிலான எதிரிகளைக் கொல்லுங்கள்.", + "Last one standing wins.": "கடைசியாக நின்றவர் வெற்றி பெறுகிறார்.", + "Last remaining alive wins.": "கடைசியாக எஞ்சியிருக்கும் வெற்றி.", + "Last team standing wins.": "கடைசி அணி வெற்றி பெருவார்", + "Prevent enemies from reaching the exit.": "எதிரிகள் வெளியேறுவதைத் தடுக்கவும்.", + "Reach the enemy flag to score.": "மதிப்பெண் பெற எதிரி கொடியை அடையுங்கள்.", + "Return the enemy flag to score.": "மதிப்பெண் பெற எதிரி கொடியை எடுத்து உங்கள் இடத்தில் வைக்கவும்.", + "Run ${ARG1} laps.": "${ARG1} சுற்றில் ஓடு.", + "Run ${ARG1} laps. Your entire team has to finish.": "${ARG1} சுற்றில் ஓடு. உங்கள் முழு அணியும் முடிக்க வேண்டும்.", + "Run 1 lap.": "1 சுற்று ஓடு.", + "Run 1 lap. Your entire team has to finish.": "1 சுற்று ஓடு. உங்கள் முழு அணியும் முடிக்க வேண்டும்.", + "Run real fast!": "மிக வேகமாக ஓடு!", + "Score ${ARG1} goals.": "${ARG1} கொள்களை பெரு.", + "Score ${ARG1} touchdowns.": "${ARG1} டச் டவுன்களை பெரு.", + "Score a goal.": "ஒரு கோளை பெரு.", + "Score a touchdown.": "ஒரு டச் டவுனை பெரு.", + "Score some goals.": "சில கோல்களை பெரு.", + "Secure all ${ARG1} flags.": "அனைத்து ${ARG1} கொடிகளையும் பாதுகாக்கவும்.", + "Secure all flags on the map to win.": "வெற்றி பெற வரைபடத்தில் உள்ள அனைத்து கொடிகளையும் பாதுகாக்கவும்.", + "Secure the flag for ${ARG1} seconds.": "கொடியை ${ARG1} வினாடிகளுக்கு பாதுகாக்கவும்.", + "Secure the flag for a set length of time.": "ஒரு குறிப்பிட்ட நேரத்திற்கு கொடியை பாதுகாக்கவும்.", + "Steal the enemy flag ${ARG1} times.": "எதிரி கொடியை ${ARG1} முறை திருடவும்.", + "Steal the enemy flag.": "எதிரி கொடியை திருடு.", + "There can be only one.": "ஒன்று மட்டுமே இருக்க முடியும்.", + "Touch the enemy flag ${ARG1} times.": "எதிரி கொடியை ${ARG1} முறை தொடவும்.", + "Touch the enemy flag.": "எதிரி கொடியை தொடவும்.", + "carry the flag for ${ARG1} seconds": "கொடியை ${ARG1} வினாடிகளுக்கு எடுத்துச் செல்லவும்", + "kill ${ARG1} enemies": "${ARG1} எதிரிகளை கொல்லுங்கள்", + "last one standing wins": "கடைசியாக நின்றவர் வெற்றி பெறுகிறது", + "last team standing wins": "கடைசியாக அணி வெற்றி பெருவார்", + "return ${ARG1} flags": "${ARG1} கொடிகளைத் திருப்பித் தரவும்", + "return 1 flag": "1 கொடி திரும்ப வைக்கவும்", + "run ${ARG1} laps": "${ARG1} சுற்றில் ஓடு", + "run 1 lap": "1 சுற்று ஓடு", + "score ${ARG1} goals": "${ARG1} கொல்களை பெரு", + "score ${ARG1} touchdowns": "${ARG1} டச் டவுன்களை பெரு", + "score a goal": "ஒரு கோளை பெரு", + "score a touchdown": "ஒரு டச் டவுனை பெரு", + "secure all ${ARG1} flags": "அனைத்து ${ARG1} கொடிகளையும் பாதுகாக்கவும்", + "secure the flag for ${ARG1} seconds": "கொடியை ${ARG1} வினாடிகளுக்கு பாதுகாக்கவும்", + "touch ${ARG1} flags": "${ARG1} கொடிகளைத் தொடவும்", + "touch 1 flag": "1 கொடியை தொடவும்" + }, + "gameNames": { + "Assault": "அஸ்ஸலட்", + "Capture the Flag": "காப்சர் தி பிளாக்", + "Chosen One": "சொசன் ஒன்று", + "Conquest": "கான்குஎஸ்த்", + "Death Match": "மரண விளையாட்டு", + "Easter Egg Hunt": "ஈஸ்டர் முட்டை வேட்டை", + "Elimination": "நீக்குதல்", + "Football": "கால்பந்து", + "Hockey": "ஹாக்கி", + "Keep Away": "ஒதுக்கி வைக்கவும்", + "King of the Hill": "மலையின் அரசன்", + "Meteor Shower": "எரிகல் பொழிவு", + "Ninja Fight": "நிஞ்ஜா சண்டை", + "Onslaught": "தாக்குதல்", + "Race": "ஓட்டப்பந்தயம்", + "Runaround": "சுற்றி ஓடு", + "Target Practice": "இலக்கு பயிற்சி", + "The Last Stand": "கடைசி நிலைபாடு" + }, + "inputDeviceNames": { + "Keyboard": "விசைப்பலகை", + "Keyboard P2": "விசைப்பலகை P2" + }, + "languages": { + "Arabic": "அரேபிக்", + "Belarussian": "பெளருஷ்ஷியன்", + "Chinese": "சைனீஸ் எளிமைப்படுத்தப்பட்டது", + "ChineseTraditional": "சைனீஸ் பாரம்பரியமான", + "Croatian": "கிரோட்டியன்", + "Czech": "சேக்", + "Danish": "டானிஷ்", + "Dutch": "டச்", + "English": "ஆங்கிலம்", + "Esperanto": "எசபராண்டோ", + "Finnish": "பின்னிஷ்", + "French": "பிரெஞ்ச்", + "German": "ஜெர்மன்", + "Gibberish": "கிப்பேரிஷ்", + "Greek": "கிரீக்", + "Hindi": "ஹிந்தி", + "Hungarian": "ஹங்கரியன்", + "Indonesian": "இன்தோனேஷியன்", + "Italian": "இத்தாலியன்", + "Japanese": "ஜாபனீஸ்", + "Korean": "கொரியன்", + "Persian": "பர்ஷியன்", + "Polish": "பலிஷ்", + "Portuguese": "போர்சுகிஸ்", + "Romanian": "ரோமானியன்", + "Russian": "ரஷ்யன்", + "Serbian": "சர்பியன்", + "Slovak": "ஸ்லோவக்", + "Spanish": "ஸ்பானிஷ்", + "Swedish": "ஸ்வீடிஷ்", + "Thai": "தாய்", + "Turkish": "டர்கிஷ்", + "Ukrainian": "உக்ரைனியன்", + "Venetian": "வெநெடியன்", + "Vietnamese": "வியெட்னமீஸ்" + }, + "leagueNames": { + "Bronze": "பிரான்ஸ்", + "Diamond": "டைமன்ட்", + "Gold": "தங்கம்", + "Silver": "வெள்ளி" + }, + "mapsNames": { + "Big G": "Big G", + "Bridgit": "பிரிட்சிட்", + "Courtyard": "கோர்ட்யார்ட்", + "Crag Castle": "கிரக் கோட்டை", + "Doom Shroom": "டூம் ஷரூம்", + "Football Stadium": "கால்பந்து அரங்கம்", + "Happy Thoughts": "ஹாப்பி தொட்ஸ்", + "Hockey Stadium": "ஹாக்கி ஸ்டேடியம்", + "Lake Frigid": "பிரிசித் ஏரி", + "Monkey Face": "குரங்கு முகம்", + "Rampage": "ராம்பேஜ்", + "Roundabout": "ரவுண்டபவுட்", + "Step Right Up": "ஸ்டெப் ரைட் அப்", + "The Pad": "தி பாட்", + "Tip Top": "டிப் டாப்", + "Tower D": "டவர் D", + "Zigzag": "ஜிக்ஜாக்" + }, + "playlistNames": { + "Just Epic": "வெறும் காவியம்", + "Just Sports": "வெறும் விளையாட்டு" + }, + "scoreNames": { + "Flags": "கொடிகள்", + "Goals": "கோள்கள்", + "Score": "மதிப்பெண்கள்", + "Survived": "உயிர் பிழைத்தது", + "Time": "நேரம்", + "Time Held": "நேரம் நடைபெற்றது" + }, + "serverResponses": { + "A code has already been used on this account.": "இந்தக் கணக்கில் ஏற்கனவே ஒரு குறியீடு பயன்படுத்தப்பட்டுள்ளது.", + "A reward has already been given for that address.": "அந்த முகவரிக்கு ஏற்கனவே வெகுமதி வழங்கப்பட்டுள்ளது.", + "Account linking successful!": "கணக்கு இணைப்பு வெற்றிகரமாக உள்ளது!", + "Account unlinking successful!": "கணக்கு இணைப்பு நீக்கப்பட்டது!", + "Accounts are already linked.": "கணக்குகள் ஏற்கனவே இணைக்கப்பட்டுள்ளன.", + "Ad view could not be verified.\nPlease be sure you are running an official and up-to-date version of the game.": "விளம்பரக் காட்சியைச் சரிபார்க்க முடியவில்லை.\nநீங்கள் விளையாட்டின் அதிகாரப்பூர்வ மற்றும் புதுப்பித்த பதிப்பை இயக்குகிறீர்கள் என்பதை உறுதிப்படுத்திக் கொள்ளுங்கள்.", + "An error has occurred; (${ERROR})": "தவறு நிகழ்ந்துவிட்டது; (${ERROR})", + "An error has occurred; please contact support. (${ERROR})": "தவறு நிகழ்ந்துவிட்டது; தயவுசெய்து ஆதரவைத் தொடர்பு கொள்ளவும். (${ERROR})", + "An error has occurred; please contact support@froemling.net.": "தவறு நிகழ்ந்துவிட்டது; தயவுசெய்து support@froemling.net ஐ தொடர்பு கொள்ளவும்.", + "An error has occurred; please try again later.": "தவறு நிகழ்ந்துவிட்டது; தயவுசெய்து பிறகு முயற்சிக்கவும்.", + "Are you sure you want to link these accounts?\n\n${ACCOUNT1}\n${ACCOUNT2}\n\nThis cannot be undone!": "இந்தக் கணக்குகளை நிச்சயமாக இணைக்க விரும்புகிறீர்களா?\n\n${ACCOUNT1}\n${ACCOUNT2}\n\nஇதை முடிக்காமல் விட கூடாது!", + "BombSquad Pro unlocked!": "பாம்ஸ்குவாட் ப்ரோ அன்லாக் செய்யபட்தது!", + "Can't link 2 accounts of this type.": "இந்த வகை 2 கணக்குகளை இணைக்க முடியாது.", + "Can't link 2 diamond league accounts.": "2 வைர லீக் கணக்குகளை இணைக்க முடியாது.", + "Can't link; would surpass maximum of ${COUNT} linked accounts.": "இணைக்க முடியவில்லை; அதிகபட்சமாக ${COUNT} இணைக்கப்பட்ட கணக்குகளை விட அதிகமாக இருக்கும்.", + "Cheating detected; scores and prizes suspended for ${COUNT} days.": "மோசடி கண்டறியப்பட்டது; மதிப்பெண்கள் மற்றும் பரிசுகள் ${COUNT} நாட்களுக்கு நிறுத்தி வைக்கப்பட்டன.", + "Could not establish a secure connection.": "பாதுகாப்பான இணைப்பை நிறுவ முடியவில்லை.", + "Daily maximum reached.": "தினசரி அதிகபட்சம் அடைந்தது.", + "Entering tournament...": "போட்டியில் நுழைகிறது...", + "Invalid code.": "தவறான குறியீடு.", + "Invalid payment; purchase canceled.": "தவறான கட்டணம்; கொள்முதல் ரத்து செய்யப்பட்டது.", + "Invalid promo code.": "தவறான விளம்பர குறியீடு.", + "Invalid purchase.": "தவறான கொள்முதல்.", + "Invalid tournament entry; score will be ignored.": "தவறான போட்டி நுழைவு; மதிப்பெண் புறக்கணிக்கப்படும்.", + "Item unlocked!": "பொருள் அன்லோக் செய்யபட்தது", + "LINKING DENIED. ${ACCOUNT} contains\nsignificant data that would ALL BE LOST.\nYou can link in the opposite order if you'd like\n(and lose THIS account's data instead)": "இணைத்தல் மறுக்கப்பட்டது. ${ACCOUNT} கொண்டுள்ளது\nஎல்லாவற்றையும் இழக்கும் குறிப்பிடத்தக்க தரவு.\nநீங்கள் விரும்பினால் எதிர் வரிசையில் இணைக்கலாம்\n(அதற்கு பதிலாக இந்த கணக்கின் தரவை இழக்கவும்)", + "Link account ${ACCOUNT} to this account?\nAll existing data on ${ACCOUNT} will be lost.\nThis can not be undone. Are you sure?": "இந்தக் கணக்கிற்கு ${ACCOUNT} கணக்கை இணைக்கவா?\n${ACCOUNT} இல் இருக்கும் எல்லா தரவும் இழக்கப்படும்.\nஇதை முடிக்காமல் விட கூடாது. நீ சொல்வது உறுதியா?", + "Max number of playlists reached.": "அதிகபட்ச எண்ணிக்கையிலான பிளேலிஸ்ட்கள் எட்டப்பட்டுள்ளன.", + "Max number of profiles reached.": "அதிகபட்ச எண்ணிக்கையிலான சுயவிவரங்கள் எட்டப்பட்டுள்ளன.", + "Maximum friend code rewards reached.": "அதிகபட்ச நண்பர் குறியீடு வெகுமதிகளை அடைந்தது.", + "Message is too long.": "செய்தி மிக நீளமானது.", + "No servers are available. Please try again soon.": "சேவையகங்கள் இல்லை. தயவுசெய்து விரைவில் மீண்டும் முயற்சிக்கவும்.", + "Profile \"${NAME}\" upgraded successfully.": "சுயவிவரம் \"${NAME}\" வெற்றிகரமாக மேம்படுத்தப்பட்டது.", + "Profile could not be upgraded.": "சுயவிவரத்தை மேம்படுத்த முடியவில்லை.", + "Purchase successful!": "வாங்குதல் வெற்றிகரமாக உள்ளது!", + "Received ${COUNT} tickets for signing in.\nCome back tomorrow to receive ${TOMORROW_COUNT}.": "உள்நுழைவதற்கு ${COUNT} டிக்கெட்டுகள் கிடைத்தன.\n${TOMORROW_COUNT} பெற நாளை மீண்டும் வாருங்கள்.", + "Server functionality is no longer supported in this version of the game;\nPlease update to a newer version.": "விளையாட்டின் இந்த பதிப்பில் சேவையக செயல்பாடு இனி ஆதரிக்கப்படாது;\nதயவுசெய்து புதிய பதிப்பிற்கு புதுப்பிக்கவும்.", + "Sorry, there are no uses remaining on this code.": "மன்னிக்கவும், இந்த குறியீட்டில் எந்த பயனும் இல்லை.", + "Sorry, this code has already been used.": "மன்னிக்கவும், இந்த குறியீடு ஏற்கனவே பயன்படுத்தப்பட்டுள்ளது.", + "Sorry, this code has expired.": "மன்னிக்கவும், இந்த குறியீடு காலாவதியாகிவிட்டது.", + "Sorry, this code only works for new accounts.": "மன்னிக்கவும், இந்தக் குறியீடு புதிய கணக்குகளுக்கு மட்டுமே வேலை செய்யும்.", + "Still searching for nearby servers; please try again soon.": "இன்னும் அருகிலுள்ள சேவையகங்களைத் தேடுகிறது; தயவுசெய்து விரைவில் மீண்டும் முயற்சிக்கவும்.", + "Temporarily unavailable; please try again later.": "தற்காலிகமாக இல்லை; தயவுசெய்து பிறகு முயற்சிக்கவும்.", + "The tournament ended before you finished.": "நீங்கள் முடிப்பதற்குள் போட்டி முடிந்தது.", + "This account cannot be unlinked for ${NUM} days.": "இந்தக் கணக்கை ${NUM} நாட்களுக்கு இணைக்க முடியவில்லை.", + "This code cannot be used on the account that created it.": "இந்தக் குறியீட்டை உருவாக்கிய கணக்கில் பயன்படுத்த முடியாது.", + "This is currently unavailable; please try again later.": "இது தற்போது கிடைக்கவில்லை; தயவுசெய்து பிறகு முயற்சிக்கவும்.", + "This requires version ${VERSION} or newer.": "இதற்கு ${VERSION} பதிப்பு அல்லது புதியது தேவை.", + "Tournaments disabled due to rooted device.": "வேரூன்றிய சாதனம் காரணமாக போட்டிகள் முடக்கப்பட்டுள்ளன.", + "Tournaments require ${VERSION} or newer": "போட்டிகளுக்கு ${VERSION} அல்லது புதியது தேவை", + "Unlink ${ACCOUNT} from this account?\nAll data on ${ACCOUNT} will be reset.\n(except for achievements in some cases)": "இந்தக் கணக்கிலிருந்து ${ACCOUNT} இணைப்பை நீக்கவா?\n${ACCOUNT} இல் உள்ள எல்லா தரவும் மீட்டமைக்கப்படும்.\n(சில சந்தர்ப்பங்களில் சாதனைகளைத் தவிர)", + "WARNING: complaints of hacking have been issued against your account.\nAccounts found to be hacking will be banned. Please play fair.": "எச்சரிக்கை: உங்கள் கணக்கிற்கு எதிராக ஹேக்கிங் புகார்கள் வழங்கப்பட்டுள்ளன.\nஹேக்கிங் செய்யப்பட்ட கணக்குகள் தடை செய்யப்படும். தயவுசெய்து நியாயமாக விளையாடுங்கள்.", + "Would you like to link your device account to this one?\n\nYour device account is ${ACCOUNT1}\nThis account is ${ACCOUNT2}\n\nThis will allow you to keep your existing progress.\nWarning: this cannot be undone!\n": "உங்கள் சாதனக் கணக்கை இதனுடன் இணைக்க விரும்புகிறீர்களா?\n\nஉங்கள் சாதனக் கணக்கு ${ACCOUNT1}\nஇந்தக் கணக்கு ${ACCOUNT2}\n\nஇது உங்கள் தற்போதைய முன்னேற்றத்தை வைத்திருக்க அனுமதிக்கும்.\nஎச்சரிக்கை: இதைச் செயல்தவிர்க்க முடியாது!", + "You already own this!": "நீங்கள் ஏற்கனவே இதை வைத்திருக்கிறீர்கள்!", + "You can join in ${COUNT} seconds.": "நீங்கள் ${COUNT} வினாடிகளில் சேரலாம்.", + "You don't have enough tickets for this!": "இதற்கு உங்களிடம் போதுமான டிக்கெட்டுகள் இல்லை!", + "You don't own that.": "உங்களுக்கு அது சொந்தமில்லை.", + "You got ${COUNT} tickets!": "நீங்கள் ${COUNT} டிக்கெட்டுகளைப் பெற்றுள்ளீர்கள்!", + "You got a ${ITEM}!": "உங்களுக்கு ${ITEM} கிடைத்துள்ளது!", + "You have been promoted to a new league; congratulations!": "நீங்கள் ஒரு புதிய லீக்கில் பதவி உயர்வு பெற்றுள்ளீர்கள்; வாழ்த்துக்கள்!", + "You must update to a newer version of the app to do this.": "இதைச் செய்ய நீங்கள் பயன்பாட்டின் புதிய பதிப்பைப் புதுப்பிக்க வேண்டும்.", + "You must update to the newest version of the game to do this.": "இதைச் செய்ய நீங்கள் விளையாட்டின் புதிய பதிப்பைப் புதுப்பிக்க வேண்டும்.", + "You must wait a few seconds before entering a new code.": "புதிய குறியீட்டை உள்ளிடுவதற்கு சில வினாடிகள் காத்திருக்க வேண்டும்.", + "You ranked #${RANK} in the last tournament. Thanks for playing!": "கடந்த போட்டியில் #${RANK} இடத்தைப் பிடித்தீர்கள். விளையாடியதற்கு நன்றி!", + "Your account was rejected. Are you signed in?": "உங்கள் கணக்கு நிராகரிக்கப்பட்டது. நீங்கள் உள்நுழைந்துள்ளீர்களா?", + "Your copy of the game has been modified.\nPlease revert any changes and try again.": "விளையாட்டின் உங்கள் நகல் மாற்றப்பட்டது.\nதயவுசெய்து ஏதேனும் மாற்றங்களைச் செய்து, மீண்டும் முயற்சிக்கவும்.", + "Your friend code was used by ${ACCOUNT}": "உங்கள் நண்பர் குறியீடு ${ACCOUNT} ஆல் பயன்படுத்தப்பட்டது" + }, + "settingNames": { + "1 Minute": "1 நிமிடம்", + "1 Second": "1 வினாடி", + "10 Minutes": "10 நிமிடங்கள்", + "2 Minutes": "2 நிமிடங்கள்", + "2 Seconds": "2 வினாடிகள்", + "20 Minutes": "20 நிமிடங்கள்", + "4 Seconds": "4 வினாடிகள்", + "5 Minutes": "5 நிமிடங்கள்", + "8 Seconds": "8 வினாடிகள்", + "Allow Negative Scores": "எதிர்மறை மதிப்பெண்களை அனுமதிக்கவும்", + "Balance Total Lives": "இருப்பு மொத்த உயிர்கள்", + "Bomb Spawning": "வெடிகுண்டு முட்டையிடுதல்", + "Chosen One Gets Gloves": "தேர்ந்தெடுக்கப்பட்ட ஒருவர் கையுறைகளைப் பெறுகிறார்", + "Chosen One Gets Shield": "தேர்ந்தெடுக்கப்பட்ட ஒருவர் கேடயத்தைப் பெறுகிறார்", + "Chosen One Time": "தேர்ந்தெடுக்கப்பட்டது ஒன்றின் நேரம்", + "Enable Impact Bombs": "தாக்க குண்டுகளை இயக்கு", + "Enable Triple Bombs": "மூன்று குண்டுகளை இயக்கு", + "Entire Team Must Finish": "முழு குழுவும் முடிக்க வேண்டும்", + "Epic Mode": "காவிய முறை", + "Flag Idle Return Time": "கொடி சும்மா திரும்பும் நேரம்", + "Flag Touch Return Time": "கொடியைத் தொடும் நேரம்", + "Hold Time": "நேரத்தை பிடி", + "Kills to Win Per Player": "ஒரு வீரருக்கு வெல்ல பலி", + "Laps": "சுற்றுகள்", + "Lives Per Player": "ஒரு வீரருக்கு உயிர்கள்", + "Long": "பெரிய", + "Longer": "நீண்ட", + "Mine Spawning": "சின்ன குண்டு முட்டையிடும்", + "No Mines": "சின்ன குண்டுகள் இல்லை", + "None": "இல்லை", + "Normal": "சாதாரண", + "Pro Mode": "ப்ரோ முறை", + "Respawn Times": "ரேஸ்பான் நேரங்கள்", + "Score to Win": "மதிப்பெண் பெரு வெற்றி பெறுவாய்", + "Short": "சிறிய", + "Shorter": "குறுகிய", + "Solo Mode": "தனி முறை", + "Target Count": "இலக்கு எண்ணிக்கை", + "Time Limit": "நேரம் அளவு" + }, + "statements": { + "${TEAM} is disqualified because ${PLAYER} left": "${PLAYER} வெளியேறியதால் ${TEAM} தகுதி நீக்கம் செய்யப்பட்டுள்ளது", + "Killing ${NAME} for skipping part of the track!": "பாதையின் ஒரு பகுதியைத் தவிர்த்ததற்காக ${NAME} ஐக் கொன்றது!", + "Warning to ${NAME}: turbo / button-spamming knocks you out.": "${NAME}க்கு எச்சரிக்கை: டர்போ / பட்டன்-ஸ்பேமிங் உங்களைத் தாக்கும்." + }, + "teamNames": { + "Bad Guys": "கெட்டவர்கள்", + "Blue": "நீலம்", + "Good Guys": "நல்லவர்கள்", + "Red": "சிவப்பு" + }, + "tips": { + "A perfectly timed running-jumping-spin-punch can kill in a single hit\nand earn you lifelong respect from your friends.": "ஒரு சரியான நேர ஓட்டம்-ஜம்பிங்-ஸ்பின்-பஞ்ச் ஒரே வெற்றியில் கொல்லலாம்\nமற்றும் உங்கள் நண்பர்களிடமிருந்து உங்களுக்கு வாழ்நாள் முழுவதும் மரியாதை கிடைக்கும்.", + "Always remember to floss.": "எப்போதும் floss செய்ய நினைவில் கொள்ளுங்கள்.", + "Create player profiles for yourself and your friends with\nyour preferred names and appearances instead of using random ones.": "உங்களுக்கும் உங்கள் நண்பர்களுக்கும் பிளேயர் சுயவிவரங்களை உருவாக்கவும்\nசீரற்றவற்றைப் பயன்படுத்துவதற்குப் பதிலாக உங்கள் விருப்பமான பெயர்கள் மற்றும் தோற்றங்கள்.", + "Curse boxes turn you into a ticking time bomb.\nThe only cure is to quickly grab a health-pack.": "சாபப் பெட்டிகள் உங்களை ஒரு டிக்கிங் டைம் பாம்டாக மாற்றும்.\nஒரே ஒரு ஹெல்த் பேக் சீக்கிரம் பிடிப்பதுதான்.", + "Despite their looks, all characters' abilities are identical,\nso just pick whichever one you most closely resemble.": "அவற்றின் தோற்றம் இருந்தபோதிலும், அனைத்து கதாபாத்திரங்களின் திறன்களும் ஒரே மாதிரியானவை,\nஎனவே நீங்கள் மிகவும் நெருக்கமாக ஒத்திருப்பதைத் தேர்ந்தெடுக்கவும்.", + "Don't get too cocky with that energy shield; you can still get yourself thrown off a cliff.": "அந்த ஆற்றல் கவசத்துடன் மிகவும் மெல்ல வேண்டாம்; நீங்கள் இன்னும் உங்களை ஒரு குன்றிலிருந்து தூக்கி எறியலாம்.", + "Don't run all the time. Really. You will fall off cliffs.": "எல்லா நேரமும் ஓடாதே. உண்மையில். நீங்கள் பாறைகளில் இருந்து விழுவீர்கள்.", + "Don't spin for too long; you'll become dizzy and fall.": "அதிக நேரம் சுற்ற வேண்டாம்; நீங்கள் மயக்கமடைந்து விழுவீர்கள்.", + "Hold any button to run. (Trigger buttons work well if you have them)": "ஓடதற்கு ஏதேனும் பட்டனை அழுத்திப் பிடிக்கவும். (தூண்டுதல் பட்டன்கள் உங்களிடம் இருந்தால் நன்றாக வேலை செய்யும்)", + "Hold down any button to run. You'll get places faster\nbut won't turn very well, so watch out for cliffs.": "ஓட எந்த பட்டனையும் அழுத்திப் பிடிக்கவும். நீங்கள் விரைவாக இடங்களைப் பெறுவீர்கள்\nஆனால் அது நன்றாக மாறாது, எனவே பாறைகளை கவனிக்கவும்.", + "Ice bombs are not very powerful, but they freeze\nwhoever they hit, leaving them vulnerable to shattering.": "பனி குண்டுகள் மிகவும் சக்திவாய்ந்தவை அல்ல, ஆனால் அவை உறைகின்றன\nஅவர்கள் யாரை அடித்தாலும், அவர்களை நொறுக்குவதற்கு ஆளாக்கலாம்.", + "If someone picks you up, punch them and they'll let go.\nThis works in real life too.": "யாராவது உங்களை அழைத்துச் சென்றால், அவர்களை குத்துங்கள், அவர்கள் விட்டுவிடுவார்கள்.\nஇது நிஜ வாழ்க்கையிலும் வேலை செய்கிறது.", + "If you are short on controllers, install the '${REMOTE_APP_NAME}' app\non your mobile devices to use them as controllers.": "உங்களுக்கு கன்ட்ரோலர்கள் குறைவாக இருந்தால், '${REMOTE_APP_NAME}' பயன்பாட்டை நிறுவவும்\nஅவற்றை கட்டுப்படுத்திகளாகப் பயன்படுத்த உங்கள் மொபைல் சாதனங்களில்.", + "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.": "உங்களிடம் ஒட்டும் வெடிகுண்டு சிக்கினால், சுற்றி குதித்து வட்டமாகச் சுழற்றுங்கள். நீங்கள் வேண்டுமானால்\nவெடிகுண்டை அசைக்கவும், அல்லது வேறு எதுவும் இல்லையென்றால் உங்கள் கடைசி தருணங்கள் பொழுதுபோக்காக இருக்கும்.", + "If you kill an enemy in one hit you get double points for it.": "ஒரே அடியில் எதிரியைக் கொன்றால் அதற்கு இரட்டைப் புள்ளிகள் கிடைக்கும்.", + "If you pick up a curse, your only hope for survival is to\nfind a health powerup in the next few seconds.": "நீங்கள் ஒரு சாபத்தைத் தேர்ந்தெடுத்தால், உங்கள் உயிர் பிழைப்பதற்கான ஒரே நம்பிக்கை\nஅடுத்த சில நொடிகளில் ஒரு ஹெல்த் சக்தியைக் கண்டறியவும்.", + "If you stay in one place, you're toast. Run and dodge to survive..": "நீங்கள் ஒரு இடத்தில் இருந்தாள், நீங்கள் சிற்றுண்டி. உயிர் பிழைக்க ஓடி ஓடிவிடு ..", + "If you've got lots of players coming and going, turn on 'auto-kick-idle-players'\nunder settings in case anyone forgets to leave the game.": "உங்களிடம் நிறைய வீரர்கள் வந்து செல்லும் போது, ​​'ஆட்டோ-கிக்-ஐடில்-பிளேயர்கள்' என்பதை இயக்கவும்\nயாராவது விளையாட்டை விட்டு வெளியேற மறந்தால் அமைப்புகளின் கீழ்.", + "If your device gets too warm or you'd like to conserve battery power,\nturn down \"Visuals\" or \"Resolution\" in Settings->Graphics": "உங்கள் சாதனம் மிகவும் சூடாக இருந்தால் அல்லது நீங்கள் பேட்டரி சக்தியை சேமிக்க விரும்பினால்,\nஅமைப்புகள்-> கிராபிக்ஸில் \"காட்சிகள்\" அல்லது \"தீர்மானம்\" நிராகரிக்கவும்", + "If your framerate is choppy, try turning down resolution\nor visuals in the game's graphics settings.": "உங்கள் ஃப்ரேம்ரேட் மோசமாக இருந்தால், தீர்மானத்தை நிராகரிக்க முயற்சிக்கவும்\nஅல்லது விளையாட்டின் கிராபிக்ஸ் அமைப்புகளில் காட்சிகள்.", + "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.": "Capture-the-Flag இல், உங்கள் சொந்தக் கொடி மற்றொன்று என்றால், மதிப்பெண் பெற, உங்கள் அடித்தளத்தில் இருக்க வேண்டும்\nஅணி கோல் அடிக்க உள்ளது, அவர்களின் கொடியை திருடுவது அவர்களை தடுக்க ஒரு நல்ல வழியாகும்.", + "In hockey, you'll maintain more speed if you turn gradually.": "ஹாக்கியில், நீங்கள் படிப்படியாகத் திரும்பினால் அதிக வேகத்தைப் பேணுவீர்கள்.", + "It's easier to win with a friend or two helping.": "ஒரு நண்பர் அல்லது இருவரின் உதவியால் வெற்றி பெறுவது எளிது.", + "Jump just as you're throwing to get bombs up to the highest levels.": "குண்டுகளை மிக உயர்ந்த நிலைக்கு எடுப்பதற்கு நீங்கள் எறிவது போல் குதிக்கவும்.", + "Land-mines are a good way to stop speedy enemies.": "சின்ன-குண்டுகள் வேகமான எதிரிகளை நிறுத்த ஒரு நல்ல வழி.", + "Many things can be picked up and thrown, including other players. Tossing\nyour enemies off cliffs can be an effective and emotionally fulfilling strategy.": "மற்ற வீரர்கள் உட்பட பல பொருள்களை எடுத்து வீசலாம். தூக்கி எறிதல்\nபாறைகளில் இருந்து உங்கள் எதிரிகள் ஒரு பயனுள்ள மற்றும் உணர்வுபூர்வமாக நிறைவேற்றும் உத்தியாக இருக்கலாம்.", + "No, you can't get up on the ledge. You have to throw bombs.": "இல்லை, நீங்கள் லெட்ஜில் எழுந்திருக்க முடியாது. குண்டுகளை வீச வேண்டும்.", + "Players can join and leave in the middle of most games,\nand you can also plug and unplug controllers on the fly.": "பெரும்பாலான கேம்களின் நடுவில் வீரர்கள் சேரலாம் மற்றும் வெளியேறலாம்,\nமேலும் நீங்கள் பறக்கும்போது கட்டுப்படுத்திகளை செருகவும் மற்றும் அகற்றவும் முடியும்.", + "Practice using your momentum to throw bombs more accurately.": "வெடிகுண்டுகளை இன்னும் துல்லியமாக வீச உங்கள் வேகத்தைப் பயன்படுத்திப் பயிற்சி செய்யுங்கள்.", + "Punches do more damage the faster your fists are moving,\nso try running, jumping, and spinning like crazy.": "உங்கள் கைமுட்டிகள் எவ்வளவு வேகமாக நகருகிறதோ, அந்த அளவுக்கு குத்துக்கள் அதிக சேதத்தை ஏற்படுத்தும்.\nஎனவே பைத்தியம் போல் ஓடவும், குதிக்கவும், சுழலவும் முயற்சிக்கவும்.", + "Run back and forth before throwing a bomb\nto 'whiplash' it and throw it farther.": "வெடிகுண்டை வீசுவதற்கு முன் முன்னும் பின்னுமாக ஓடுங்கள்\nஅதை 'சவுக்கடி' செய்து தூர எறியுங்கள்.", + "Take out a group of enemies by\nsetting off a bomb near a TNT box.": "எதிரிகளின் குழுவை வெளியேற்றவும்\nTNT பெட்டிக்கு அருகில் வெடிகுண்டு வைப்பது.", + "The head is the most vulnerable area, so a sticky-bomb\nto the noggin usually means game-over.": "தலை மிகவும் பாதிக்கப்படக்கூடிய பகுதி, எனவே ஒரு ஒட்டும் குண்டு\nto the noggin பொதுவாக கேம்-ஓவர் என்று பொருள்.", + "This level never ends, but a high score here\nwill earn you eternal respect throughout the world.": "இந்த நிலை ஒருபோதும் முடிவடையாது, ஆனால் இங்கே அதிக மதிப்பெண்\nஉலகம் முழுவதும் உங்களுக்கு நித்திய மரியாதை கிடைக்கும்.", + "Throw strength is based on the direction you are holding.\nTo toss something gently in front of you, don't hold any direction.": "வீசும் வலிமை நீங்கள் வைத்திருக்கும் திசையை அடிப்படையாகக் கொண்டது.\nஉங்கள் முன்னால் மெதுவாக எதையாவது தூக்கி எறிய, எந்த திசையையும் பிடிக்காதீர்கள்.", + "Tired of the soundtrack? Replace it with your own!\nSee Settings->Audio->Soundtrack": "ஒலிப்பதிவு சோர்வாக? அதை உங்கள் சொந்தமாக மாற்றவும்!\nஅமைப்புகள்->ஆடியோ->சவுண்ட்டிராக்கைப் பார்க்கவும்", + "Try 'Cooking off' bombs for a second or two before throwing them.": "குண்டுகளை வீசுவதற்கு முன் ஓரிரு வினாடிகளுக்கு 'குக்கிங் ஆஃப்' முயற்சி செய்யுங்கள்.", + "Try tricking enemies into killing eachother or running off cliffs.": "எதிரிகளை ஏமாற்றி ஒருவருக்கொருவர் கொல்ல அல்லது பாறைகளிலிருந்து ஓட முயற்சிக்கவும்.", + "Use the pick-up button to grab the flag < ${PICKUP} >": "< ${PICKUP} > கொடியைப் பிடிக்க பிக்-அப் பொத்தானைப் பயன்படுத்தவும்", + "Whip back and forth to get more distance on your throws..": "உங்கள் வீசுதல்களில் அதிக தூரத்தைப் பெற முன்னும் பின்னுமாக அடிக்கவும்..", + "You can 'aim' your punches by spinning left or right.\nThis is useful for knocking bad guys off edges or scoring in hockey.": "உங்கள் குத்துக்களை இடது அல்லது வலது பக்கம் சுழற்றுவதன் மூலம் 'இலக்கு' செய்யலாம்.\nகெட்டவர்களை விளிம்பிலிருந்து தட்டுவதற்கு அல்லது ஹாக்கியில் அடிப்பதற்கு இது பயனுள்ளதாக இருக்கும்.", + "You can judge when a bomb is going to explode based on the\ncolor of sparks from its fuse: yellow..orange..red..BOOM.": "ஒரு வெடிகுண்டு எப்போது வெடிக்கும் என்பதை நீங்கள் தீர்மானிக்க முடியும்\nஅதன் உருகியில் இருந்து தீப்பொறிகளின் நிறம்: மஞ்சள்..ஆரஞ்சு..சிவப்பு..பூம்.", + "You can throw bombs higher if you jump just before throwing.": "எறிவதற்கு சற்று முன் குதித்தால் குண்டுகளை உயரமாக வீசலாம்.", + "You take damage when you whack your head on things,\nso try to not whack your head on things.": "விஷயங்களில் உங்கள் தலையை அசைக்கும்போது நீங்கள் சேதமடைகிறீர்கள்,\nஎனவே விஷயங்களில் உங்கள் தலையை அசைக்காமல் இருக்க முயற்சி செய்யுங்கள்.", + "Your punches do much more damage if you are running or spinning.": "நீங்கள் ஓடினால் அல்லது சுழன்று கொண்டிருந்தால் உங்கள் குத்துக்கள் அதிக சேதத்தை ஏற்படுத்தும்." + } + }, + "trophiesRequiredText": "இதற்கு குறைந்தபட்சம் ${NUMBER} கோப்பைகள் தேவை.", + "trophiesText": "கோப்பைகள்", + "trophiesThisSeasonText": "இந்த சீசனில் கோப்பைகள்", + "tutorial": { + "cpuBenchmarkText": "நகைச்சுவையான வேகத்தில் பயிற்சியை இயக்குதல் (முதன்மையாக CPU வேகத்தை சோதிக்கிறது)", + "phrase01Text": "வணக்கம்!", + "phrase02Text": "${APP_NAME} க்கு வரவேற்கிறோம்!", + "phrase03Text": "உங்கள் தன்மையைக் கட்டுப்படுத்த சில குறிப்புகள் இங்கே:", + "phrase04Text": "${APP_NAME} இல் உள்ள பல விஷயங்கள் PHYSICS அடிப்படையிலானவை.", + "phrase05Text": "உதாரணமாக, நீங்கள் குத்தும் போது,..", + "phrase06Text": "..சேதம் உங்கள் முஷ்டிகளின் வேகத்தை அடிப்படையாகக் கொண்டது.", + "phrase07Text": "பார்க்க? நாங்கள் நகரவில்லை, அதனால் ${NAME} ஐ காயப்படுத்தவில்லை.", + "phrase08Text": "இப்போது அதிக வேகத்தைப் பெற குதித்து சுழலலாம்.", + "phrase09Text": "ஆ, அது சிறந்தது.", + "phrase10Text": "ஓடுவதும் உதவுகிறது.", + "phrase11Text": "இயக்க எந்த பொத்தானையும் அழுத்திப் பிடிக்கவும்.", + "phrase12Text": "கூடுதல் அற்புதமான குத்துகளுக்கு, ஓடவும் சுழலவும் முயற்சிக்கவும்.", + "phrase13Text": "அச்சச்சோ; ${NAME} பற்றி மன்னிக்கவும்.", + "phrase14Text": "கொடிகள் .. அல்லது ${NAME} போன்றவற்றை எடுத்து எறியலாம்.", + "phrase15Text": "கடைசியாக, குண்டுகள் உள்ளன.", + "phrase16Text": "வெடிகுண்டுகளை வீசுவது பயிற்சி தேவை.", + "phrase17Text": "அச்சச்சோ! மிகவும் நல்ல வீசுதல் அல்ல.", + "phrase18Text": "நகர்த்துவது தூரத்தை எறிய உதவுகிறது.", + "phrase19Text": "குதிப்பது உயரத்தை எறிய உதவுகிறது.", + "phrase20Text": "உங்கள் குண்டுகளை இன்னும் நீண்ட தூரத்திற்கு \"சவுக்கடி\".", + "phrase21Text": "உங்கள் குண்டுகளை டைமிங் செய்வது தந்திரமானதாக இருக்கலாம்.", + "phrase22Text": "Dang.", + "phrase23Text": "ஒன்றிரண்டு அல்லது இரண்டு நிமிடங்களுக்கு உருகி \"Cooking off\" முயற்சிக்கவும்.", + "phrase24Text": "ஹூரே! நன்றாக போடப்பட்டது", + "phrase25Text": "சரி, அது பற்றி தான்.", + "phrase26Text": "இப்போது அவர்களைப் போய் புலி!", + "phrase27Text": "உங்கள் பயிற்சியை நினைவில் கொள்ளுங்கள், நீங்கள் உயிருடன் திரும்பி வருவீர்கள்!", + "phrase28Text": "...நன்று,இருக்கலாம்...", + "phrase29Text": "நல்ல அதிர்ஷ்டம்!", + "randomName1Text": "ஃப்ரெட்", + "randomName2Text": "ஹரி", + "randomName3Text": "பில்", + "randomName4Text": "சக்", + "randomName5Text": "பில்", + "skipConfirmText": "டுடோரியலை உண்மையில் தவிர்க்க வேண்டுமா? உறுதிப்படுத்த தட்டவும் அல்லது அழுத்தவும்.", + "skipVoteCountText": "${COUNT}/${TOTAL} வாக்குகளை தவிர்க்கவும்", + "skippingText": "பயிற்சியைத் தவிர்க்கிறது...", + "toSkipPressAnythingText": "(பயிற்சியைத் தவிர்க்க எதையும் தட்டவும் அல்லது அழுத்தவும்)" + }, + "twoKillText": "இரட்டை கொலை!", + "unavailableText": "கிடைக்கவில்லை", + "unconfiguredControllerDetectedText": "கட்டமைக்கப்படாத கட்டுப்படுத்தி கண்டறியப்பட்டது:", + "unlockThisInTheStoreText": "இதை ஸ்டோரில் திறக்க வேண்டும்.", + "unlockThisProfilesText": "${NUM} க்கும் அதிகமான சுயவிவரங்களை உருவாக்க, உங்களுக்கு இது தேவை:", + "unlockThisText": "இதைத் திறக்க, உங்களுக்குத் தேவை:", + "unsupportedHardwareText": "மன்னிக்கவும், இந்த வன்பொருள் விளையாட்டின் உருவாக்கத்தால் ஆதரிக்கப்படவில்லை.", + "upFirstText": "முதலில் மேலே:", + "upNextText": "${COUNT} விளையாட்டில் அடுத்தது:", + "updatingAccountText": "உங்கள் கணக்கை புதுப்பிக்கிறது...", + "upgradeText": "மேம்படுத்தல்", + "upgradeToPlayText": "இதை விளையாட கேம் ஸ்டோரில் \"${PRO}\" ஐத் திறக்கவும்.", + "useDefaultText": "இயல்புநிலையைப் பயன்படுத்தவும்", + "usesExternalControllerText": "இந்த விளையாட்டு உள்ளீட்டிற்கு வெளிப்புற கட்டுப்படுத்தியைப் பயன்படுத்துகிறது.", + "usingItunesText": "ஒலிப்பதிவுக்காக மியூசிக் ஆப் பயன்படுத்துகிறது...", + "validatingTestBuildText": "சோதனை கட்டத்தை சரிபார்க்கிறது...", + "victoryText": "வெற்றி!", + "voteDelayText": "நீங்கள் மற்றொரு வாக்கை ${NUMBER} வினாடிகளுக்குத் தொடங்க முடியாது", + "voteInProgressText": "வாக்கெடுப்பு ஏற்கனவே நடந்து கொண்டிருக்கிறது.", + "votedAlreadyText": "நீங்கள் ஏற்கனவே வாக்களித்துள்ளீர்கள்", + "votesNeededText": "${NUMBER} வாக்குகள் தேவை", + "vsText": "எதிராக.", + "waitingForHostText": "(${HOST} தொடர காத்திருக்கிறது)", + "waitingForPlayersText": "வீரர்கள் சேர காத்திருக்கிறார்கள்...", + "waitingInLineText": "வரிசையில் காத்திருக்கிறேன் (விருந்து நிரம்பியுள்ளது)...", + "watchAVideoText": "ஒரு வீடியோவைப் பாருங்கள்", + "watchAnAdText": "ஒரு விளம்பரத்தைப் பாருங்கள்", + "watchWindow": { + "deleteConfirmText": "\"${REPLAY}\" ஐ நீக்கவா?", + "deleteReplayButtonText": "நீக்கு\nமறு", + "myReplaysText": "என் ரீப்ளேஸ்", + "noReplaySelectedErrorText": "மறுபதிவு தேர்ந்தெடுக்கப்படவில்லை", + "playbackSpeedText": "பின்னணி வேகம்: ${SPEED}", + "renameReplayButtonText": "மறுபெயரிடு\nமறு", + "renameReplayText": "\"${REPLAY}\" என மறுபெயரிடுங்கள்:", + "renameText": "மறுபெயரிடு", + "replayDeleteErrorText": "ரீப்ளேயை நீக்குவதில் பிழை.", + "replayNameText": "ரீப்ளே பெயர்", + "replayRenameErrorAlreadyExistsText": "அந்த பெயரில் ஒரு ரீப்ளே ஏற்கனவே உள்ளது.", + "replayRenameErrorInvalidName": "மறுபடியும் மறுபெயரிட முடியாது; தவறான பெயர்.", + "replayRenameErrorText": "மறுபெயருக்கு மறுபெயரிடுவதில் பிழை.", + "sharedReplaysText": "பகிரப்பட்ட ரீப்ளேஸ்", + "titleText": "பார்க்க", + "watchReplayButtonText": "பார்க்க\nமறு" + }, + "waveText": "அலை", + "wellSureText": "சரி நிச்சயமாக!", + "wiimoteLicenseWindow": { + "titleText": "டார்வின் ரிமோட் பதிப்புரிமை" + }, + "wiimoteListenWindow": { + "listeningText": "வைமோட்களைக் கேட்கிறது...", + "pressText": "வைமோட் பட்டன்கலை 1 மற்றும் 2 ஐ ஒரே நேரத்தில் அழுத்தவும்.", + "pressText2": "மோஷன் பிளஸ் உள்ளமைக்கப்பட்ட புதிய வைமோட்களில், அதற்குப் பின்னால் உள்ள சிவப்பு 'ஒத்திசைவு' பொத்தானை அழுத்தவும்." + }, + "wiimoteSetupWindow": { + "copyrightText": "டார்வின் ரிமோட் பதிப்புரிமை", + "listenText": "கேளுங்கள்", + "macInstructionsText": "உங்கள் வை முடக்கப்பட்டு, ப்ளூடூத் இயக்கப்பட்டிருப்பதை உறுதிசெய்க\nஉங்கள் மேக்கில், 'கேளுங்கள்' என்பதை அழுத்தவும். Wiimote ஆதரவு முடியும்\nசற்று மெல்லியதாக இருக்கும், எனவே நீங்கள் சில முறை முயற்சி செய்ய வேண்டியிருக்கும்\nநீங்கள் ஒரு இணைப்பைப் பெறுவதற்கு முன்.\n\nபுளூடூத் 7 இணைக்கப்பட்ட சாதனங்களை கையாள வேண்டும்,\nஇருந்தாலும் உங்கள் மைலேஜ் மாறுபடலாம்.\n\nBombSquad அசல் Wiimotes, Nunchuks ஐ ஆதரிக்கிறது,\nமற்றும் கிளாசிக் கன்ட்ரோலர்.\nபுதிய Wii ரிமோட் பிளஸ் இப்போது கூட வேலை செய்கிறது\nஆனால் இணைப்புகளுடன் அல்ல.", + "thanksText": "டார்வின் ரிமோட் குழுவுக்கு நன்றி\nஇதை சாத்தியப்படுத்தியதற்காக.", + "titleText": "விமோட் அமைப்பு" + }, + "winsPlayerText": "${NAME} வெற்றி!", + "winsTeamText": "${NAME} வெற்றி!", + "winsText": "${NAME} வெற்றி!", + "worldScoresUnavailableText": "உலக ஸ்கோர் கிடைக்கவில்லை.", + "worldsBestScoresText": "உலகின் சிறந்த மதிப்பெண்கள்", + "worldsBestTimesText": "உலகின் சிறந்த நேரங்கள்", + "xbox360ControllersWindow": { + "getDriverText": "Driver ஐ பெற", + "macInstructions2Text": "கட்டுப்படுத்திகளை கம்பியில்லாமல் பயன்படுத்த, உங்களுக்கு ரிசீவரும் தேவை\nவிண்டோஸிற்கான எக்ஸ்பாக்ஸ் 360 வயர்லெஸ் கன்ட்ரோலருடன் வருகிறது.\nஒரு ரிசீவர் உங்களை 4 கட்டுப்படுத்திகளை இணைக்க அனுமதிக்கிறது.\n\nமுக்கியமானது: 3 வது தரப்பு பெறுநர்கள் இந்த டிரைவருடன் வேலை செய்ய மாட்டார்கள்;\nஉங்கள் ரிசீவர் அதில் 'மைக்ரோசாப்ட்' என்று கூறுவதை உறுதி செய்து கொள்ளுங்கள், 'எக்ஸ்பாக்ஸ் 360' அல்ல.\nமைக்ரோசாப்ட் இனி தனித்தனியாக விற்காது, எனவே நீங்கள் பெற வேண்டும்\nகட்டுப்பாட்டாளருடன் தொகுக்கப்பட்ட ஒன்று அல்லது வேறு ஈபேயைத் தேடுங்கள்.\n\nஇது உங்களுக்கு பயனுள்ளதாக இருந்தால், தயவுசெய்து ஒரு நன்கொடையைக் கருத்தில் கொள்ளவும்\nஅவரது தளத்தில் டிரைவர் டெவலப்பர்.", + "macInstructionsText": "எக்ஸ்பாக்ஸ் 360 கட்டுப்படுத்திகளைப் பயன்படுத்த, நீங்கள் நிறுவ வேண்டும்\nமேக் டிரைவர் கீழே உள்ள இணைப்பில் கிடைக்கிறது.\nஇது கம்பி மற்றும் வயர்லெஸ் கட்டுப்படுத்திகளுடன் வேலை செய்கிறது.", + "ouyaInstructionsText": "BombSquad உடன் கம்பி எக்ஸ்பாக்ஸ் 360 கட்டுப்படுத்திகளைப் பயன்படுத்த, வெறுமனே\nஅவற்றை உங்கள் சாதனத்தின் USB போர்ட்டில் செருகவும். நீங்கள் ஒரு USB ஹப் பயன்படுத்தலாம்\nபல கட்டுப்படுத்திகளை இணைக்க.\n\nவயர்லெஸ் கன்ட்ரோலர்களைப் பயன்படுத்த உங்களுக்கு வயர்லெஸ் ரிசீவர் தேவை,\n\"விண்டோஸிற்கான எக்ஸ்பாக்ஸ் 360 வயர்லெஸ் கன்ட்ரோலர்\" இன் ஒரு பகுதியாக கிடைக்கிறது\nதொகுப்பு அல்லது தனித்தனியாக விற்கப்படுகிறது. ஒவ்வொரு ரிசீவரும் USB போர்ட்டில் செருகப்படுகிறது மற்றும்\n4 வயர்லெஸ் கட்டுப்படுத்திகளை இணைக்க உங்களை அனுமதிக்கிறது.", + "titleText": "${APP_NAME} உடன் எக்ஸ்பாக்ஸ் 360 கட்டுப்பாட்டுகளைப் பயன்படுத்துதல்:" + }, + "yesAllowText": "ஆம், அனுமதி!", + "yourBestScoresText": "உங்கள் சிறந்த மதிப்பெண்கள்", + "yourBestTimesText": "உங்கள் சிறந்த நேரங்கள்" +} \ No newline at end of file diff --git a/dist/ba_data/data/languages/thai.json b/dist/ba_data/data/languages/thai.json new file mode 100644 index 0000000..3dd2e77 --- /dev/null +++ b/dist/ba_data/data/languages/thai.json @@ -0,0 +1,1850 @@ +{ + "accountSettingsWindow": { + "accountNameRules": "ชื่อบัญชีต้องไม่มีอิโมจิหรือตัวอักษรพิเศษอื่นๆ", + "accountsText": "บัญชี", + "achievementProgressText": "ผ่านความสำเร็จ: ${COUNT} จาก ${TOTAL}", + "campaignProgressText": "ความคืบหน้าในภารกิจ [ยาก]: ${PROGRESS}", + "changeOncePerSeason": "คุณสามารถเปลี่ยนได้เพียงครั้งเดียวต่อฤดูกาล", + "changeOncePerSeasonError": "คุณต้องรอจนกว่าฤดูกาลหน้าจะเปลี่ยนอีก (${NUM} days)", + "customName": "ชื่อที่กำหนดเอง", + "linkAccountsEnterCodeText": "ใส่รหัส", + "linkAccountsGenerateCodeText": "สร้างรหัส", + "linkAccountsInfoText": "(ใช้ความคืบหน้าร่วมกันกับแพลตฟอร์มอื่นๆ)", + "linkAccountsInstructionsNewText": "หากต้องการเชื่อมโยงสองบัญชีให้สร้างรหัสในบัญชีแรกและป้อนรหัสนั้นในวินาที ข้อมูลจากบัญชีที่สองจะถูกแชร์ระหว่างทั้งสอง(ข้อมูลจากบัญชีแรกจะหายไป): ${COUNT} ** .", + "linkAccountsInstructionsText": "เพื่อที่จะผูกทั้งสองบัญชีเข้าด้วยกัน จะต้องสร้างรหัสรหัสหนึ่ง\nแล้วจะต้องใส่รหัสในบัญที่คุณต้องการจะเชื่อมโยงเข้าด้วยกัน\nความคืบหน้าและสิ่งของในคลังของทั้งสองบัญชีจะถูกรวมกัน\nคุณสามารถเชื่อมโยงได้ทั้งหมด ${COUNT} บัญชี\n\nระวัง! หลังจากผูกบัญชีแล้วจะไม่สามารถยกเลิกได้!", + "linkAccountsText": "ผูกบัญชี", + "linkedAccountsText": "บัญชีที่เชื่อมโยงแล้ว", + "nameChangeConfirm": "คุณต้องการเปลี่ยนชื่อบัญชีของคุณเป็น ${NAME} หรือไม่", + "resetProgressConfirmNoAchievementsText": "การทำสิ่งนี้จะรีเซ็ตความคืบหน้าต่างๆ ในโหมด co-op\nและคะแนนดีที่สุดในอุปกรณ์นี้ (แต่จะไม่รีเซ็ตตั๋วของคุณ) \nการทำสิ่งนี้ไม่สามารถยกเลิกได้! คุณแน่ใจหรือไม่?", + "resetProgressConfirmText": "การทำสิ่งนี้จะรีเซ็ตความคืบหน้าในโหมด co-op,\nความสำเร็จและคะแนนดีที่สุดในอุปกรณ์นี้\n(แต่จะไม่รีเซ็ตตั๋วของคุณ) การทำสิ่งนี้จะ\nไม่สามารถยกเลิกได้! คุณแน่ใจหรือไม่?", + "resetProgressText": "รีเซ็ตความคืบหน้า", + "setAccountName": "ตั้งชื่อบัญชี", + "setAccountNameDesc": "เลือกชื่อที่จะแสดงสำหรับบัญชีของคุณคุณสามารถใช้ชื่อจากหนึ่งในการเชื่อมโยงของคุณบัญชีหรือสร้างชื่อที่กำหนดเองที่ไม่ซ้ำกัน:{Thanakorn}", + "signInInfoText": "เข้าสู่ระบบเพื่อเก็บตั๋วในการเล่นแบบออนไลน์,\nและแบ่งปันความคืบหน้าของคุณกับอุปกรณ์อื่นๆ", + "signInText": "ลงชื่อเข้าใช้", + "signInWithDeviceInfoText": "(บัญชีอัตโนมัติที่ใช้ได้เฉพาะในอุปกรณ์นี้)", + "signInWithDeviceText": "เข้าสู่ระบบจากอุปกรณ์นี้", + "signInWithGameCircleText": "เข้าสู่ระบบด้วยบัญชี Game Circle", + "signInWithGooglePlayText": "ลงชื่อเข้าใช้ด้วยบัญชี Google Play", + "signInWithTestAccountInfoText": "(บัญชีทดลอง;เลือกตัวเลือกนี้เพื่อไปต่อ)", + "signInWithTestAccountText": "ลงชื่อเข้าใช้เพื่อทดลอง", + "signOutText": "ออกจากระบบ", + "signingInText": "กำลังลงชื่อเข้าใช้...", + "signingOutText": "กำลังออกจากระบบ...", + "ticketsText": "ตั๋ว: ${COUNT}", + "titleText": "บัญชี", + "unlinkAccountsInstructionsText": "เลือกบัญชีที่จะยกเลิกการเชื่อมโยง", + "unlinkAccountsText": "ยกเลิกการเชื่อมโยงบัญชี", + "viaAccount": "ผ่านบัญชี ${NAME}", + "youAreSignedInAsText": "คุณลงชื่อเข้าใช้ในบัญชี:" + }, + "achievementChallengesText": "ท้าทายความสำเร็จ", + "achievementText": "ความสำเร็จ", + "achievements": { + "Boom Goes the Dynamite": { + "description": "ฆ่าศัตรู 3 คนด้วย TNT", + "descriptionComplete": "ฆ่าศัตรู 3 คนด้วย TNT แล้ว", + "descriptionFull": "ฆ่าศัตรู ${LEVEL} 3 คนด้วย TNT", + "descriptionFullComplete": "ฆ่าศัตรู 3 คน ด้วย TNT ใน ${LEVEL}", + "name": "ระเบิดดังสนั่นหวั่นไหวจากไดนาไมต์" + }, + "Boxer": { + "description": "ชนะโดยไม่ใช้ระเบิด", + "descriptionComplete": "ชนะโดยไม่ใช้ระเบิดแล้ว", + "descriptionFull": "ชนะ ${LEVEL} โดยไม่ใช้ระเบิด", + "descriptionFullComplete": "ชนะ ${LEVEL} โดยไม่ใช้ระเบิดแล้ว", + "name": "นักมวย" + }, + "Dual Wielding": { + "descriptionFull": "เชื่อมต่อกับคอนโทรลเลอร์สองตัว", + "descriptionFullComplete": "เชื่อมต่อกับคอนโทรลเลอร์สองตัวแล้ว", + "name": "ถนัดสองมือ" + }, + "Flawless Victory": { + "description": "ชนะโดยไม่ได้รับความเสียหายเลย", + "descriptionComplete": "ชนะแล้วโดยไม่ได้รับความเสียหายเลย", + "descriptionFull": "ชนะ ${LEVEL} โดยไม่ได้รับความเสียหายเลย", + "descriptionFullComplete": "ชนะ ${LEVEL} แล้วโดยไม่ได้รับความเสียหายเลย", + "name": "ชนะอย่างไร้ที่ติ" + }, + "Free Loader": { + "descriptionFull": "เริ่มเล่นโหมด Free-For-All กับผู้เล่นมากกว่า 2 คน", + "descriptionFullComplete": "เริ่มเล่นโหมด Free-For-All กับผู้เล่นมากกว่า 2 คนแล้ว", + "name": "เล่นเดี่ยว" + }, + "Gold Miner": { + "description": "ฆ่าศัตรู 6 คนด้วยกับระเบิด", + "descriptionComplete": "ฆ่าศัตรู 6 คนด้วยกับระเบิดแล้ว", + "descriptionFull": "ฆ่าศัตรู 6 คนด้วยกับระเบิดใน ${LEVEL}", + "descriptionFullComplete": "ฆ่าศัตรู 6 คนด้วยกับระเบิดใน ${LEVEL} แล้ว", + "name": "นักขุดเหมืองทอง" + }, + "Got the Moves": { + "description": "ชนะโดยไม่ต้องใช้หมัดหรือระเบิด", + "descriptionComplete": "ชนะโดยไม่ต้องใช้หมัดหรือระเบิดแล้ว", + "descriptionFull": "ชนะ ${LEVEL} โดยไม่ต้องใช้หมัดหรือระเบิด", + "descriptionFullComplete": "ชนะ ${LEVEL} โดยไม่ต้องใช้หมัดหรือระเบิดแล้ว", + "name": "เร่งรีบ" + }, + "In Control": { + "descriptionFull": "เชื่อมต่อกับคอนโทรลเลอร์", + "descriptionFullComplete": "เชื่อมต่อกับคอนโทรลเลอร์แล้ว", + "name": "อยู่ในการควบคุม" + }, + "Last Stand God": { + "description": "ทำคะแนน 1000 คะแนน", + "descriptionComplete": "ทำคะแนน 1000 คะแนนแล้ว", + "descriptionFull": "ทำคะแนน 1000 คะแนนใน ${LEVEL}", + "descriptionFullComplete": "ทำคะแนน 1000 คะแนนใน ${LEVEL} แล้ว", + "name": "เทพ ${LEVEL}" + }, + "Last Stand Master": { + "description": "ทำคะแนน 250 คะแนน", + "descriptionComplete": "ทำคะแนน 250 คะแนนแล้ว", + "descriptionFull": "ทำคะแนน 250 คะแนนใน ${LEVEL}", + "descriptionFullComplete": "ทำคะแนน 250 คะแนนใน ${LEVEL} แล้ว", + "name": "ผู้เชี่ยวชาญ ${LEVEL}" + }, + "Last Stand Wizard": { + "description": "ทำคะแนน 500 คะแนน", + "descriptionComplete": "ทำคะแนน 500 คะแนนแล้ว", + "descriptionFull": "ทำคะแนน 500 คะแนนใน ${LEVEL}", + "descriptionFullComplete": "ทำคะแนน 500 คะแนนใน ${LEVEL} แล้ว", + "name": "ผู้วิเศษ ${LEVEL}" + }, + "Mine Games": { + "description": "ฆ่าศัตรู 3 คนด้วยกับระเบิด", + "descriptionComplete": "ฆ่าศัตรู 3 คนด้วยกับระเบิดแล้ว", + "descriptionFull": "ฆ่าศัตรู 3 คนด้วยกับระเบิดใน ${LEVEL}", + "descriptionFullComplete": "ฆ่าศัตรู 3 คนด้วยกับระเบิดใน ${LEVEL} แล้ว", + "name": "เกมกับระเบิด" + }, + "Off You Go Then": { + "description": "โยนศัตรู 3 คนลงออกจากแมพ", + "descriptionComplete": "โยนศัตรู 3 คนลงออกจากแมพแล้ว", + "descriptionFull": "โยนศัตรู 3 คนลงออกจากแมพใน ${LEVEL}", + "descriptionFullComplete": "โยนศัตรู 3 คนลงออกจากแมพใน ${LEVEL} แล้ว", + "name": "ออกไปให้พ้น" + }, + "Onslaught God": { + "description": "ทำคะแนน 5000 คะแนน", + "descriptionComplete": "ทำคะแนน 5000 คะแนนแล้ว", + "descriptionFull": "ทำคะแนน 5000 คะแนนใน ${LEVEL}", + "descriptionFullComplete": "ทำคะแนน 5000 คะแนนใน ${LEVEL} แล้ว", + "name": "เทพ ${LEVEL}" + }, + "Onslaught Master": { + "description": "ทำคะแนน 500 คะแนน", + "descriptionComplete": "ทำคะแนน 500 คะแนนแล้ว", + "descriptionFull": "ทำคะแนน 500 คะแนนใน ${LEVEL}", + "descriptionFullComplete": "ทำคะแนน 500 คะแนนใน ${LEVEL} แล้ว", + "name": "ผู้เชี่ยวชาญ ${LEVEL}" + }, + "Onslaught Training Victory": { + "description": "เอาชนะทุกรอบในด่าน", + "descriptionComplete": "เอาชนะทุกรอบในด่านแล้ว", + "descriptionFull": "เอาชนะทุกรอบใน ${LEVEL}", + "descriptionFullComplete": "เอาชนะทุกรอบใน ${LEVEL} แล้ว", + "name": "ชนะ ${LEVEL}" + }, + "Onslaught Wizard": { + "description": "ทำคะแนน 1000 คะแนน", + "descriptionComplete": "ทำคะแนน 1000 คะแนนแล้ว", + "descriptionFull": "ทำคะแนน 1000 คะแนนใน ${LEVEL}", + "descriptionFullComplete": "ทำคะแนน 1000 คะแนนใน ${LEVEL} แล้ว", + "name": "ผู้วิเศษ ${LEVEL}" + }, + "Precision Bombing": { + "description": "ชนะโดยไม่เก็บไอเทมวิเศษ", + "descriptionComplete": "ชนะโดยไม่เก็บไอเทมวิเศษแล้ว", + "descriptionFull": "ชนะ ${LEVEL} โดยไม่เก็บไอเทมวิเศษ", + "descriptionFullComplete": "ชนะ ${LEVEL} โดยไม่เก็บไอเทมวิเศษแล้ว", + "name": "นักระเบิดที่แม่นยำ" + }, + "Pro Boxer": { + "description": "ชนะโดยไม่ใช้ระเบิด", + "descriptionComplete": "ชนะโดยไม่ใช้ระเบิดแล้ว", + "descriptionFull": "ชนะ ${LEVEL} โดยไม่ใช้ระเบิด", + "descriptionFullComplete": "ชนะ ${LEVEL} โดยไม่ใช้ระเบิดแล้ว", + "name": "นักมวยมือโปร" + }, + "Pro Football Shutout": { + "description": "ชนะโดยไม่ให้ศัตรูทำคะแนน", + "descriptionComplete": "ชนะโดยไม่ให้ศัตรูทำคะแนนแล้ว", + "descriptionFull": "ชนะ ${LEVEL} โดยไม่ให้ศัตรูทำคะแนน", + "descriptionFullComplete": "ชนะ ${LEVEL} โดยไม่ให้ศัตรูทำคะแนนแล้ว", + "name": "${LEVEL} ห้ามเข้า" + }, + "Pro Football Victory": { + "description": "ชนะเกม", + "descriptionComplete": "ชนะเกมแล้ว", + "descriptionFull": "ชนะเกมใน ${LEVEL}", + "descriptionFullComplete": "ชนะเกมใน ${LEVEL} แล้ว", + "name": "ชนะ ${LEVEL}" + }, + "Pro Onslaught Victory": { + "description": "เอาชนะทุกรอบในด่าน", + "descriptionComplete": "เอาชนะทุกรอบในด่านแล้ว", + "descriptionFull": "เอาชนะทุกรอบใน ${LEVEL}", + "descriptionFullComplete": "เอาชนะทุกรอบใน ${LEVEL}", + "name": "ชนะ ${LEVEL}" + }, + "Pro Runaround Victory": { + "description": "เอาชนะทุกรอบในด่าน", + "descriptionComplete": "เอาชนะทุกรอบในด่านแล้ว", + "descriptionFull": "เอาชนะทุกรอบใน ${LEVEL}", + "descriptionFullComplete": "เอาชนะทุกรอบใน ${LEVEL} แล้ว", + "name": "ชนะ ${LEVEL}" + }, + "Rookie Football Shutout": { + "description": "ชนะโดยไม่ให้ศัตรูทำคะแนน", + "descriptionComplete": "ชนะโดยไม่ให้ศัตรูทำคะแนนแล้ว", + "descriptionFull": "ชนะ ${LEVEL} โดยไม่ให้ศัตรูทำคะแนน", + "descriptionFullComplete": "ชนะ ${LEVEL} โดยไม่ให้ศัตรูทำคะแนนแล้ว", + "name": "${LEVEL} ไม่ให้เข้า" + }, + "Rookie Football Victory": { + "description": "ชนะเกม", + "descriptionComplete": "ชนะเกมแล้ว", + "descriptionFull": "ชนะเกมใน ${LEVEL}", + "descriptionFullComplete": "ชนะเกมใน ${LEVEL} แล้ว", + "name": "ชนะ ${LEVEL}" + }, + "Rookie Onslaught Victory": { + "description": "เอาชนะทุกรอบในด่าน", + "descriptionComplete": "เอาชนะทุกรอบในด่านแล้ว", + "descriptionFull": "เอาชนะทุกรอบใน ${LEVEL}", + "descriptionFullComplete": "เอาชนะทุกรอบใน ${LEVEL} แล้ว", + "name": "ชนะ ${LEVEL}" + }, + "Runaround God": { + "description": "ทำคะแนน 2000 คะแนน", + "descriptionComplete": "ทำคะแนน 2000 คะแนนแล้ว", + "descriptionFull": "ทำคะแนน 2000 คะแนนใน ${LEVEL}", + "descriptionFullComplete": "ทำคะแนน 2000 คะแนนใน ${LEVEL} แล้ว", + "name": "เทพ ${LEVEL}" + }, + "Runaround Master": { + "description": "ทำคะแนน 500 คะแนน", + "descriptionComplete": "ทำคะแนน 500 คะแนนแล้ว", + "descriptionFull": "ทำคะแนน 500 คะแนนใน ${LEVEL}", + "descriptionFullComplete": "ทำคะแนน 500 คะแนนใน ${LEVEL} แล้ว", + "name": "ผู้เชี่ยวชาญ ${LEVEL}" + }, + "Runaround Wizard": { + "description": "ทำคะแนน 1000 คะแนน", + "descriptionComplete": "ทำคะแนน 1000 คะแนนแล้ว", + "descriptionFull": "ทำคะแนน 1000 คะแนนใน ${LEVEL}", + "descriptionFullComplete": "ทำคะแนน 1000 คะแนนใน ${LEVEL} แล้ว", + "name": "ผู้วิเศษ ${LEVEL}" + }, + "Sharing is Caring": { + "descriptionFull": "แบ่งปันเกมกับเพื่อนสำเร็จ", + "descriptionFullComplete": "ได้แบ่งปันเกมกับเพื่อนสำเร็จแล้ว", + "name": "แบ่งปันคือการดูแล" + }, + "Stayin' Alive": { + "description": "ชนะโดยไม่ตายเลย", + "descriptionComplete": "ชนะโดยไม่ตายเลยแล้ว", + "descriptionFull": "ชนะ ${LEVEL} โดยไม่ตายเลย", + "descriptionFullComplete": "ชนะ ${LEVEL} โดยไม่ตายเลยแล้ว", + "name": "ยังมีชีวิตอยู่" + }, + "Super Mega Punch": { + "description": "ทำดาเมจ 100% ด้วยหมัดเดียว", + "descriptionComplete": "ทำดาเมจ 100% ด้วยหมัดเดียวแล้ว", + "descriptionFull": "ทำดาเมจ 100% ด้วยหมัดเดียวใน ${LEVEL}", + "descriptionFullComplete": "ทำดาเมจ 100% ด้วยหมัดเดียวใน ${LEVEL} แล้ว", + "name": "สุดยอดแห่งหมัด" + }, + "Super Punch": { + "description": "ทำดาเมจ 50% ด้วยหมัดเดียว", + "descriptionComplete": "ทำดาเมจ 50% ด้วยหมัดเดียวแล้ว", + "descriptionFull": "ทำดาเมจ 50% ด้วยหมัดเดียวใน ${LEVEL}", + "descriptionFullComplete": "ทำดาเมจ 50% ด้วยหมัดเดียวใน ${LEVEL} แล้ว", + "name": "หมัดที่รุนแรง" + }, + "TNT Terror": { + "description": "ฆ่าศัตรู 6 คนด้วย TNT", + "descriptionComplete": "ฆ่าศัตรู 6 คนด้วย TNT แล้ว", + "descriptionFull": "ฆ่าศัตรู 6 คนด้วย TNT ใน ${LEVEL}", + "descriptionFullComplete": "ฆ่าศัตรู 6 คนด้วย TNT ใน ${LEVEL} แล้ว", + "name": "นักวางระเบิด" + }, + "Team Player": { + "descriptionFull": "เริ่มเล่นเกมแบบทีมกับผู้เล่น 4 คนขึ้นไป", + "descriptionFullComplete": "เริ่มเล่นเกมแบบทีมกับผู้เล่น 4 คนขึ้นไปแล้ว", + "name": "เล่นเป็นทีม" + }, + "The Great Wall": { + "description": "หยุดศัตรูทุกคน", + "descriptionComplete": "หยุดศัตรูทุกคนแล้ว", + "descriptionFull": "หยุดศัตรูทุกคนใน ${LEVEL}", + "descriptionFullComplete": "หยุดศัตรูทุกคนใน ${LEVEL} แล้ว", + "name": "กำแพงที่แข็งแกร่ง" + }, + "The Wall": { + "description": "หยุดศัตรูทุกคน", + "descriptionComplete": "หยุดศัตรูทุกคนแล้ว", + "descriptionFull": "หยุดศัตรูทุกคนใน ${LEVEL}", + "descriptionFullComplete": "หยุดศัตรูทุกคนใน ${LEVEL} แล้ว", + "name": "กำแพง" + }, + "Uber Football Shutout": { + "description": "ชนะโดยไม่ให้ศัตรูทำคะแนน", + "descriptionComplete": "ชนะโดยไม่ให้ศัตรูทำคะแนนแล้ว", + "descriptionFull": "ชนะ ${LEVEL} โดยไม่ให้ศัตรูทำคะแนน", + "descriptionFullComplete": "ชนะ ${LEVEL} โดยไม่ให้ศัตรูทำคะแนนแล้ว", + "name": "${LEVEL} ไม่ให้เข้า" + }, + "Uber Football Victory": { + "description": "ชนะเกม", + "descriptionComplete": "ชนะเกมแล้ว", + "descriptionFull": "ชนะเกมใน ${LEVEL}", + "descriptionFullComplete": "ชนะเกมใน ${LEVEL} แล้ว", + "name": "ชนะ ${LEVEL}" + }, + "Uber Onslaught Victory": { + "description": "เอาชนะทุกรอบในด่าน", + "descriptionComplete": "เอาชนะทุกรอบในด่านแล้ว", + "descriptionFull": "เอาชนะทุกรอบใน ${LEVEL}", + "descriptionFullComplete": "เอาชนะทุกรอบใน ${LEVEL} แล้ว", + "name": "ชนะ ${LEVEL}" + }, + "Uber Runaround Victory": { + "description": "เอาชนะทุกรอบในด่าน", + "descriptionComplete": "เอาชนะทุกรอบในด่านแล้ว", + "descriptionFull": "เอาชนะทุกรอบใน ${LEVEL}", + "descriptionFullComplete": "เอาชนะทุกรอบใน ${LEVEL} แล้ว", + "name": "ชนะ ${LEVEL}" + } + }, + "achievementsRemainingText": "ความสำเร็จที่ยังเหลืออยู่:", + "achievementsText": "ความสำเร็จ", + "achievementsUnavailableForOldSeasonsText": "ขออภัย, ความสำเร็จนี้ไม่ได้มีอยู่ในฤดูกาลเก่า", + "addGameWindow": { + "getMoreGamesText": "รับเกมเพิ่มเติม", + "titleText": "เพิ่มเกม" + }, + "allowText": "ยอมรับ", + "alreadySignedInText": "บัญชีของคุณลงชื่อเข้าใช้จากอุปกรณ์อื่น\nโปรดเปลี่ยนบัญชีหรือปิดเกมของคุณ\nอุปกรณ์อื่นและลองอีกครั้ง", + "apiVersionErrorText": "ไม่สามารถโหลดโมดูล ${NAME} ได้; มันอยู่ในเวอร์ชั่น api ${VERSION_USED}; แต่เราต้องการเวอร์ชั่น ${VERSION_REQUIRED}", + "audioSettingsWindow": { + "headRelativeVRAudioInfoText": "(\"อัติโนมัติ\" เปิดสิ่งนี้เฉพาะตอนที่เสียบหูฟังอยู่เท่านั้น)", + "headRelativeVRAudioText": "ระดับเสียงเครื่องสวมหัว VR", + "musicVolumeText": "ระดับเสียงเพลง", + "soundVolumeText": "ระดับเสียงประกอบ", + "soundtrackButtonText": "เพลงประกอบ", + "soundtrackDescriptionText": "(นำเพลงของคุณเองมาใช้ในการเล่นเกม)", + "titleText": "การตั้งค่าเสียง" + }, + "autoText": "อัติโนมัติ", + "backText": "ย้อนกลับ", + "banThisPlayerText": "ผู้เล่นคนนี้โดนแบน", + "bestOfFinalText": "ดีที่สุดจาก ${COUNT} รอบ", + "bestOfSeriesText": "ดีที่สุดจาก ${COUNT} ฤดูกาล:", + "bestRankText": "อันดับที่ดีที่สุดของคุณคือ #${RANK}", + "bestRatingText": "เรตติ้งที่ดีที่สุดของคุณคือ ${RATING}", + "bombBoldText": "'ระเบิด'", + "bombText": "ระเบิด", + "boostText": "บูสท์", + "bsRemoteConfigureInAppText": "${REMOTE_APP_NAME} กำลังค่าในตัวแอพเอง", + "buttonText": "ปุ่ม", + "canWeDebugText": "คุณอยากให้ BombSquad แจ้งข้อผิดพลาด ปัญหา\nและข้อมูลการใช้งานทั่วไปกับผู้สร้างโดยอัติโนมัติหรือไม่?\n\nข้อมูลนี้จะไม่เกี่ยวข้องกับข้อมูลส่วนตัวและจะช่วยให้\nสามารถเล่นเกมได้อย่างราบรื่นและไม่มีข้อผิดพลาด", + "cancelText": "ยกเลิก", + "cantConfigureDeviceText": "ขออภัย, ${DEVICE} ไม่สามารถตั้งค่าได้", + "challengeEndedText": "การท้าทายนี้ได้จบลงแล้ว", + "chatMuteText": "ปิดเสียงแชท", + "chatMutedText": "ปิดเสียงการแชท", + "chatUnMuteText": "ยกเลิกการปิดเสียงแขท", + "choosingPlayerText": "<กำลังเลือกผู้เล่น>", + "completeThisLevelToProceedText": "คุณต้องผ่านด่านนี้\nก่อนจึงจะไปต่อได้!", + "completionBonusText": "รางวัลการผ่านด่าน", + "configControllersWindow": { + "configureControllersText": "ตั้งค่าคอนโทรลเลอร์", + "configureKeyboard2Text": "ตั้งค่าคีย์บอร์ดผู้เล่นคนที่ 2", + "configureKeyboardText": "ตั้งค่าคีย์บอร์ด", + "configureMobileText": "ใช้โทรศัพท์มือถือเป็นคอนโทรลเลอร์", + "configureTouchText": "ตั้งค่าหน้าจอสัมผัส", + "ps3Text": "คอนโทรลเลอร์ PS3", + "titleText": "คอนโทรลเลอร์", + "wiimotesText": "Wiimotes", + "xbox360Text": "คอนโทรลเลอร์ Xbox 360" + }, + "configGamepadSelectWindow": { + "androidNoteText": "โน๊ต: คอนโทรลเลอร์ที่สนับสนุนจะแตกต่างกันไปตามอุปกรณ์และเวอร์ชั่นแอนดรอยด์", + "pressAnyButtonText": "กดปุ่มใดก็ได้บนคอนโทรลเลอร์\nที่คุณต้องการจะตั้งค่า...", + "titleText": "ตั้งค่าคอนโทรลเลอร์" + }, + "configGamepadWindow": { + "advancedText": "ขั้นสูง", + "advancedTitleText": "การตั้งค่าคอนโทรลเลอร์ขั้นสูง", + "analogStickDeadZoneDescriptionText": "(เปิดสิ่งนี้ถ้าตัวละครของคุณ 'ซัดเซ' เวลาที่คุณปล่อยการควบคุม)", + "analogStickDeadZoneText": "Dead Zone จอยอนาล็อก", + "appliesToAllText": "(ใช้ได้กับทุกคอนโทรลเลอร์ที่เป็นประเภทนี้)", + "autoRecalibrateDescriptionText": "(เปิดสิ่งนี้ถ้าตัวละครของคุณขยับได้ไม่เร็วเท่าความเร็วสูงสุด)", + "autoRecalibrateText": "ปรับจอยอนาล็อกอัติโนมัติ", + "axisText": "แกน", + "clearText": "เคลียร์", + "dpadText": "dpad", + "extraStartButtonText": "ปุ่มสตาร์ทพิเศษ", + "ifNothingHappensTryAnalogText": "ถ้าไม่เกิดอะไรขึ้น, ลองกำหนดเป็นจอยอนาล็อกแทน", + "ifNothingHappensTryDpadText": "ถ้าไม่เกิดอะไรขึ้น, ลองกำหนดเป็น dpad แทน", + "ignoreCompletelyDescriptionText": "(ป้องกันคอนโทรลเลอร์ตัวนี้ไม่ให้มีผลกระทบต่อเกมหรือเมนู)", + "ignoreCompletelyText": "ละเว้นเสร็จสมบูรณ์", + "ignoredButton1Text": "ปุ่มที่ละเว้น 1", + "ignoredButton2Text": "ปุ่มที่ละเว้น 2", + "ignoredButton3Text": "ปุ่มที่ละเว้น 3", + "ignoredButton4Text": "ปุ่มที่ละเว้น 4", + "ignoredButtonDescriptionText": "(ใช้สิ่งนี้เพื่อป้องกันปุ่ม 'home' หรือ 'ซิงค์' ไม่ให้มีผลกระทบต่อ UI)", + "pressAnyAnalogTriggerText": "กดปุ่มใดก็ได้บนอนาล็อก...", + "pressAnyButtonOrDpadText": "กดปุ่มใดก็ได้หรือ dpad...", + "pressAnyButtonText": "กดปุ่มใดก็ได้...", + "pressLeftRightText": "กดปุ่มซ้ายหรือขวา...", + "pressUpDownText": "กดปุ่มขึ้นหรือลง...", + "runButton1Text": "ปุ่มวิ่ง 1", + "runButton2Text": "ปุ่มวิ่ง 2", + "runTrigger1Text": "ปุ่มวิ่ง 1", + "runTrigger2Text": "ปุ่มวิ่ง 2", + "runTriggerDescriptionText": "(ปุ่มอนาล็อกที่ทำให้คุณวิ่งในความเร็วที่เปลี่ยนได้))", + "secondHalfText": "ใช้สิ่งนี้เพื่อตั้งค่าให้ใช้คอนโทรลเลอร์\n2 อัน ใน 1 อุปกรณ์ ที่จะแสดงให้เห็น\nว่าใช้คอนโทรลเลอร์เพียงตัวเดียว", + "secondaryEnableText": "ยอมรับ", + "secondaryText": "คอนโทรลเลอร์ตัวที่ 2", + "startButtonActivatesDefaultDescriptionText": "(ปิดสิ่งนี้ถ้าปุ่มสตาร์ทของคุณเป็นมากกว่าปุ่ม 'เมนู')", + "startButtonActivatesDefaultText": "ปุ่มสตาร์ทเปิดวิดเจ็ตเพื้นฐาน", + "titleText": "การตั้งค่าคอนโทรลเลอร์", + "twoInOneSetupText": "การตั้งค่าคอนโทรลเลอร์แบบ 2 ใน 1", + "uiOnlyDescriptionText": "(ป้องกันคอนโทรลเลอร์ตัวนี้ไม่ให้ใช้เข้าร่วมเกม)", + "uiOnlyText": "จำกัดในการใช้เมนู", + "unassignedButtonsRunText": "วิ่งปุ่มที่ไม่ได้ตั้งทั้งหมด", + "unsetText": "<ไม่ได้ตั้ง>", + "vrReorientButtonText": "ปรับทิศทางปุ่ม VR" + }, + "configKeyboardWindow": { + "configuringText": "กำลังตั้งค่า ${DEVICE}", + "keyboard2NoteText": "โน็ต: คีย์บอร์ดส่วนใหญ่สามารถตั้งปุ่มได้ไม่กี่ปุ่มในครั้งเดียว\nดังนั้นการมีคีย์บอร์ดสำหรับผู้เล่นที่ 2 อาจจะทำให้เล่นได้ดีขึ้น\nถ้ามีการแยกคีย์บอร์ดมาสำหรับพวกเขาที่จะใช้\nหรือคุณจะต้องกำหนดปุ่มที่เป็นเอกลักษณ์สำหรับผู้เล่น\nทั้ง 2 คน ในกรณีนั้น" + }, + "configTouchscreenWindow": { + "actionControlScaleText": "มาตราส่วนปุ่มควบคุมการกระทำ", + "actionsText": "การกระทำ", + "buttonsText": "ปุ่ม", + "dragControlsText": "< ลากปุ่มควบคุมเพื่อเปลี่ยนตำแหน่ง >", + "joystickText": "จอยสติ๊ก", + "movementControlScaleText": "มาตราส่วนปุ่มควบคุมการเคลื่อนไหว", + "movementText": "การเคลื่อนไหว", + "resetText": "รีเซ็ต", + "swipeControlsHiddenText": "ซ่อนปุ่มแบบปัด", + "swipeInfoText": "การควบคุมแบบ 'ปัด' จะใช้เวลาเล็กน้อยในการใช้แต่\nมันจะทำให้ควบคุมแบบไม่มองปุ่มควบคุมได้ง่ายขึ้น", + "swipeText": "การปัด", + "titleText": "การตั้งค่าหน้าจอสัมผัส" + }, + "configureItNowText": "จะตั้งค่ามันตอนนี้หรือไม่?", + "configureText": "ตั้งค่า", + "connectMobileDevicesWindow": { + "amazonText": "Amazon Appstore", + "appStoreText": "App Store", + "bestResultsText": "เพื่อผลลัพท์ที่ดีที่สุดคุณจะต้องการไวไฟที่ไม่มีความล่าช้า\nคุณสามารถลดความล่าช้าได้โดยการปิดอุปกรณ์ไร้สาย,\nเล่นให้อยู่ใกล้ๆ กับเครื่องไวไฟ, และการเชื่อมต่อโดยตรง\nกับเจ้าของห้องผ่านอีเธอร์เน็ต", + "explanationText": "เพื่อที่จะใช้สมาร์ทโฟนหรือแท็บเล็ตเป็นคอนโทรลเลอร์ไร้สาย\nติ้งตั้งแอพ \"${REMOTE_APP_NAME}\" บนเครื่องนั้น กี่เครื่องก็ได้ที่\nสามารถเชื่อมต่อกับเกม ${APP_NAME} ผ่านไวไฟ และแอพก็ฟรีอีกด้วย!", + "forAndroidText": "สำหรับแอนดรอยด์:", + "forIOSText": "สำหรับ IOS:", + "getItForText": "รับ ${REMOTE_APP_NAME} สำหรับ IOS ใน Apple App Store\nหรือสำหรับแอนดรอยด์ที่ Google Play Store หรือ Amazon Appstore", + "googlePlayText": "Google Play", + "titleText": "การใช้โทรศัพท์มือถือเป็นคอนโทรลเลอร์" + }, + "continuePurchaseText": "จะเล่นต่อหรือไม่?(จ่าย ${PRICE})", + "continueText": "ดำเนินการต่อ", + "controlsText": "การควบคุม", + "coopSelectWindow": { + "activenessAllTimeInfoText": "สิ่งนี้จะไม่ได้ใช้ในการจัดอันดับแบบทุกเวลา", + "activenessInfoText": "การคูณคะแนนนี้จะเพิ่มขึ้นเวลาที่\nคุณเล่นและจะลดลงเวลาที่คุณไม่ได้เล่น", + "activityText": "กิจกรรม", + "campaignText": "โหมดภารกิจ", + "challengesInfoText": "รับรางวัลจากการผ่านมินิเกมส์\n\nเงินรางวัลและความยากจะเพิ่มขึ้นทุกครั้ง\nที่คุณผ่านการท้าทายและจะลดลงเมื่อ\nครบระยะเวลาหนึ่งหรือได้ละทิ้งการท้าทาย", + "challengesText": "การท้าทาย", + "currentBestText": "ผู้เล่นที่ดีที่สุดในขณะนี้", + "customText": "กำหนดเอง", + "entryFeeText": "ค่าใช้จ่าย", + "forfeitConfirmText": "จะละทิ้งการท้าทายนี้หรือไม่?", + "forfeitNotAllowedYetText": "การท้าทายนี้ไม่สามารถละทิ้งได้", + "forfeitText": "ละทิ้ง", + "multipliersText": "การคูณคะแนนเพิ่ม", + "nextChallengeText": "การท้าทายต่อไป", + "nextPlayText": "เล่นครั้งต่อไปได้ใน", + "ofTotalTimeText": "จาก ${TOTAL}", + "playNowText": "เล่นทันที", + "pointsText": "คะแนน", + "powerRankingFinishedSeasonUnrankedText": "(ฤดูกาลที่จบไปแล้วที่คุณไม่ได้อยู่ในอันดับ)", + "powerRankingNotInTopText": "(คุณไม่ได้อยู่ในอันดับสูงสุด ${NUMBER} อันดับ)", + "powerRankingPointsEqualsText": "= ${NUMBER} คะแนน", + "powerRankingPointsMultText": "(x ${NUMBER} คะแนน)", + "powerRankingPointsText": "${NUMBER} คะแนน", + "powerRankingPointsToRankedText": "(${CURRENT} จาก ${REMAINING} คะแนน)", + "powerRankingText": "การจัดอันดับ", + "prizesText": "รางวัล", + "proMultInfoText": "ผู้เล่นที่มีการอัพเกรดเป็น ${PRO} แล้ว\nจะได้รับคะแนนเพิ่ม ${PERCENT}% ในนี้", + "seeMoreText": "มากกว่านี้...", + "skipWaitText": "ข้ามการรอ", + "timeRemainingText": "เวลาที่เหลือ", + "toRankedText": "เพื่อให้อยู่ในอันดับ", + "totalText": "ทั้งหมด", + "tournamentInfoText": "แข่งขันเพื่อให้ได้คะแนนสูงสุด\nกับผู้เล่นคนอื่นๆ ในลีกของคุณ\n\nผู้เล่นที่อยู่ในอันดับสูงสุดจะได้รับ\nรางวัลเมื่อทัวร์นาเมนท์ได้จบลง", + "welcome1Text": "ยินดีต้อนรับสู่ ${LEAGUE} คุณสามารถเพิ่มระดับ\nลีกของคุณการรับเรตติ้งดาว การปลดล็อคความสำเร็จ \nและการชนะทัวร์นาเมนท์ให้ได้รับถ้วยรางวัล", + "welcome2Text": "คุณสามารถได้ตั๋วจากการทำกิจกรรมเดิมๆ ได้\nตัวสามารถใช้ในการปลดล็อคตัวละครใหม่ แผนที่ใหม่\nและมินิเกมใหม่ การใช้เข้าทัวร์นาเมนท์ และอีกหลายอย่าง", + "yourPowerRankingText": "อันดับของคุณ:" + }, + "copyOfText": "${NAME} ที่ถูกคัดลอก", + "createEditPlayerText": "<สร้าง/แก้ไข ผู้เล่น>", + "createText": "สร้าง", + "creditsWindow": { + "additionalAudioArtIdeasText": "เสียงเพิ่มเติม งานออกแบบ และไอเดียโดย ${NAME}", + "additionalMusicFromText": "เพลงประกอบเพิ่มเติมโดย ${NAME}", + "allMyFamilyText": "เพื่อนๆ และครอบครัวของผมที่ช่วยในการทดสอบเกม", + "codingGraphicsAudioText": "การเขียนโปรแกรม กราฟฟิก และเสียงโดย ${NAME}", + "languageTranslationsText": "ผู้ที่แปลภาษา:", + "legalText": "ลิขสิทธ์:", + "publicDomainMusicViaText": "เพลงโดเมนสาธารณะผ่าน ${NAME}", + "softwareBasedOnText": "ซอฟต์แวร์นี้เป็นส่วนหนึ่งในงานของ ${NAME}", + "songCreditText": "${TITLE} ดำเนินการโดย ${PERFORMER} เรียบเรียงโดย \n${COMPOSER}, เตรียมการโดย ${ARRANGER}, จัดทำโดย \n${PUBLISHER}, ขอบคุณนำ้ใจจาก${SOURCE}", + "soundAndMusicText": "เสียงและเพลงประกอบ:", + "soundsText": "เสียงประกอบ(${SOURCE}):", + "specialThanksText": "ขอขอบคุณเป็นพิเศษ:", + "thanksEspeciallyToText": "ขอขอบคุณ ${NAME} โดยเฉพาะอย่างยิ่ง", + "titleText": "${APP_NAME} เครดิต", + "whoeverInventedCoffeeText": "ใครก็ตามที่คิดค้นกาแฟ" + }, + "currentStandingText": "อันดับของคุณในขณะนี้คือ #${RANK}", + "customizeText": "ปรับแต่ง...", + "deathsTallyText": "ตาย ${COUNT} ครั้ง", + "deathsText": "ตาย", + "debugText": "ดีบั๊ก", + "debugWindow": { + "reloadBenchmarkBestResultsText": "โน๊ต: แนะนำให้คุณตั้งค่าใน ตั้งค่า->กราฟฟิก->ระดับภาพ เป็น 'สูง' ระหว่างที่กำลังทดสอบสิ่งนี้", + "runCPUBenchmarkText": "รันมาตรฐาน CPU", + "runGPUBenchmarkText": "รันมาตรฐาน GPU", + "runMediaReloadBenchmarkText": "รันมาตรฐานการรีโหลดสื่อ", + "runStressTestText": "รันตัวทดสอบความตึงเครียด", + "stressTestPlayerCountText": "จำนวนผู้เล่น", + "stressTestPlaylistDescriptionText": "เพลย์ลิสที่จะทดสอบความตึงเครียด", + "stressTestPlaylistNameText": "ชื่อเพลย์ลิส", + "stressTestPlaylistTypeText": "ประเภทเพลย์ลิส", + "stressTestRoundDurationText": "ระยะเวลารอบ", + "stressTestTitleText": "ทดสอบความตึงเครียด", + "titleText": "มาตรฐานและการทดสอบความตึงเครียด", + "totalReloadTimeText": "เวลารีโหลดทั้งหมด: ${TIME} (ดูใน log สำหรับข้อมูลเพิ่มเติม)" + }, + "defaultGameListNameText": "เพลย์ลิส ${PLAYMODE} พื้นฐาน", + "defaultNewGameListNameText": "เพลย์ลิส ${PLAYMODE} ของฉัน", + "deleteText": "ลบ", + "demoText": "ทดลอง", + "denyText": "ยกเลิก", + "desktopResText": "เดสก์ท็อป Res", + "difficultyEasyText": "ง่าย", + "difficultyHardOnlyText": "โหมดยากเท่านั้น", + "difficultyHardText": "ยาก", + "difficultyHardUnlockOnlyText": "ด่านนี้สามารถปลดล็อคได้เฉพาะในโหมดยากเท่านั้น\nคุณคิดว่ามันจะใช้เวลาเท่าไหร่กันล่ะ!?!?!", + "directBrowserToURLText": "กรุณาเปิดเว็บโดยตรงตาม URL นี้:", + "disableRemoteAppConnectionsText": "ปิดการใช้การเชื่อมต่อแอพรีโมต", + "disableXInputDescriptionText": "อนุญาตให้ใช้คอนโทรลเลอร์มากกว่า 4 ตัวแต่จะทำงานได้ไม่ค่อยดีเท่าไหร่", + "disableXInputText": "ปิด XInput", + "doneText": "เสร็จสิ้น", + "drawText": "เสมอ", + "duplicateText": "ทำซ้ำ", + "editGameListWindow": { + "addGameText": "เพิ่ม\nเกม", + "cantOverwriteDefaultText": "ไม่สามารถเขียนทับเพลย์ลิสปกติได้!", + "cantSaveAlreadyExistsText": "เพลย์ลิสที่ใช้ชื่อนี้มีอยู่แล้ว!", + "cantSaveEmptyListText": "ไม่สามารถบันทึกเพลย์ลิสเปล่าได้!", + "editGameText": "แก้ไข\nเกม", + "listNameText": "ชื่อเพลย์ลิส", + "nameText": "ชื่อ", + "removeGameText": "ลบ\nเกม", + "saveText": "บันทึกลิสต์", + "titleText": "แก้ไขเพลย์ลิส" + }, + "editProfileWindow": { + "accountProfileInfoText": "โปรไฟล์พิเศษนี้มีชื่อและไอคอน\nตามบัญชีของคุณ\n\n${ICONS}\n\nสร้างโปรไฟล์แบบกำหนดเองเพื่อ\nใช้ชื่ออื่นหรือไอคอนแบบกำหนดเอง", + "accountProfileText": "(โปรไฟล์บัญชี)", + "availableText": "ชื่อ \"${NAME}\" สามารถใช้ได้", + "characterText": "ตัวละคร", + "checkingAvailabilityText": "กำลังเช็คว่าชื่อ \"${NAME}\" สามรถใช้ได้หรือไม่...", + "colorText": "สี", + "getMoreCharactersText": "รับตัวละครเพิ่มเติม...", + "getMoreIconsText": "รับไอคอนเพิ่มเติม...", + "globalProfileInfoText": "โปรไฟล์สาธารณะรับประกันว่าจะใช้ชื่อที่เป็นเอกลักษณ์\nในทั่วโลก และยังสามารถใช้ไอคอนแบบกำหนดเองได้อีกด้วย", + "globalProfileText": "(โปรไฟล์สาธารณะ)", + "highlightText": "ไฮไลท์", + "iconText": "ไอคอน", + "localProfileInfoText": "โปรไฟล์ส่วนตัวจะไม่มีไอคอนและชื่อที่ใช้จะไม่รับประกันว่าเป็น\nชื่อที่เป็นเอกลักษณ์หรือไม่ อัพเกรดเป็นโปรไฟล์สาธารณะเพื่อ\nจองชื่อที่เป็นเอกลักษณ์และใช้ไอคอนกำหนดเอง", + "localProfileText": "(โปรไฟล์ส่วนตัว)", + "nameDescriptionText": "ชื่อผู้เล่น", + "nameText": "ชื่อ", + "randomText": "สุ่มชื่อ", + "titleEditText": "แก้ไขโปรไฟล์", + "titleNewText": "สร้างโปรไฟล์ใหม่", + "unavailableText": "\"${NAME}\" ไม่สามารถใช้ได้ กรุณาลองใช้ชื่ออื่น", + "upgradeProfileInfoText": "การทำสิ่งนี้จะเป็นการจองชื่อตัวละครของคุณในทั่วโลก\nและจะทำให้คุณสามารถตั้งไอคอนกำหนดเองได้", + "upgradeToGlobalProfileText": "อัพเกรดเป็นโปรไฟล์สาธารณะ" + }, + "editSoundtrackWindow": { + "cantDeleteDefaultText": "คุณไม่สามารถลบเสียงประกอบเริ่มต้นได้", + "cantEditDefaultText": "ไม่สามารถแก้ไขเสียงประกอบเริ่มต้นได้ กรุณาคัดลอกหรือสร้างอันใหม่แทน", + "cantOverwriteDefaultText": "ไม่สามารถเขียนทับเสียงประกอบพื้นฐานได้", + "cantSaveAlreadyExistsText": "เสียงประกอบที่ใช้ชื่อนั้นมีอยู่แล้ว!", + "defaultGameMusicText": "<เพลงประกอบเกมเริ่มต้น>", + "defaultSoundtrackNameText": "เสียงประกอบเริ่มต้น", + "deleteConfirmText": "ลบเสียงประกอบ:\n\n'${NAME}'?", + "deleteText": "ลบ\nเสียงประกอบ", + "duplicateText": "ทำซ้ำ\nเสียงประกอบ", + "editSoundtrackText": "แก้ไขเสียงประกอบ", + "editText": "แก้ไข\nเสียงประกอบ", + "fetchingITunesText": "กำลังเรียกรายการเพลงในแอพ Music...", + "musicVolumeZeroWarning": "คำเตือน: เสียงเพลงตั้งค่าเป็น 0", + "nameText": "ชื่อ", + "newSoundtrackNameText": "เสียงประกอบของฉัน ${COUNT}", + "newSoundtrackText": "เสียงประกอบใหม่:", + "newText": "สร้าง\nเสียงประกอบใหม่", + "selectAPlaylistText": "เลือกเพลย์ลิส:", + "selectASourceText": "แหล่งเพลง", + "testText": "ทดสอบ", + "titleText": "เสียงประกอบ", + "useDefaultGameMusicText": "เพลงประกอบเกมเริ่มต้น", + "useITunesPlaylistText": "เพลย์ลิสต์แอพเพลง", + "useMusicFileText": "ไฟล์เพลง (mp3,ฯลฯ)", + "useMusicFolderText": "โฟลเดอร์ของไฟล์เพลง" + }, + "editText": "แก้ไข", + "endText": "จบ", + "enjoyText": "ขอให้สนุก!", + "epicDescriptionFilterText": "${DESCRIPTION} ในการเคลื่อนไหวที่ช้ามากๆ", + "epicNameFilterText": "${NAME} แบบช้ามหากาฬ", + "errorAccessDeniedText": "การเข้าถึงถูกปฏิเสธ", + "errorOutOfDiskSpaceText": "พื้นที่ว่างในเครื่องหมด", + "errorText": "ข้อผิดพลาด", + "errorUnknownText": "ข้อผิดพลาดที่ไม่รู้จัก", + "exitGameText": "จะออกจาก ${APP_NAME} หรือไม่?", + "exportSuccessText": "'${NAME}' ถูกส่งออกแล้ว", + "externalStorageText": "พื้นที่ว่าง", + "failText": "ล้มเหลว", + "fatalErrorText": "โอ๊ะ! บางสิ่งบางอย่างหายหรือเสียหายไป\nกรุณาลองติดตั้งแอพใหม่อีกครั้งหรือ\nติดต่อ ${EMAIL} สำหรับความชาวยเหลือ", + "fileSelectorWindow": { + "titleFileFolderText": "เลือกไฟล์หรือโฟลเดอร์", + "titleFileText": "เลือกไฟล์", + "titleFolderText": "เลือกโฟลเดอร์", + "useThisFolderButtonText": "ใช้โฟลเดอร์นี้" + }, + "filterText": "กรอง", + "finalScoreText": "คะแนนสิ้นสุด", + "finalScoresText": "คะแนนสิ้นสุด", + "finalTimeText": "เวลาสิ้นสุด", + "finishingInstallText": "กำลังติดตั้ง กรุณารอสักครู่...", + "fireTVRemoteWarningText": "* เพื่อประสบการณ์ที่ดีขึ้น ใช้เกม\nคอนโทรลเลอร์หรือติดตั้งแอพ\n '${REMOTE_APP_NAME}' ใน\nโทรศัพท์หรือแท็บเล็ตของคุณ", + "firstToFinalText": "คนแรกที่ได้ ${COUNT} คะแนน", + "firstToSeriesText": "คนแรกที่ได้ ${COUNT} คะแนน ฤดูกาล", + "fiveKillText": "ฆ่าห้า!!!", + "flawlessWaveText": "ชนะอย่างไร้ที่ติ!", + "fourKillText": "ฆ่าสี่!!!", + "friendScoresUnavailableText": "คะแนนเพื่อนไม่สามารถใช้ได้", + "gameCenterText": "GameCenter", + "gameCircleText": "GameCircle", + "gameLeadersText": "${COUNT} อันดับแรกของเกม", + "gameListWindow": { + "cantDeleteDefaultText": "คุณไม่สามารถลบเพลย์ลิสเริ่มต้นได้", + "cantEditDefaultText": "ไม่สามารถแก้ไขเพลย์ลิสเริ่มต้นได้! คัดลอกหรือสร้างอีกอันใหม่", + "cantShareDefaultText": "คุณไม่สามารถแบ่งปันเพลย์ลิสเริ่มต้นได้", + "deleteConfirmText": "จะลบเกม \"${LIST}\" หรือไม่?", + "deleteText": "ลบ\nเพลย์ลิส", + "duplicateText": "ทำซ้ำ\nเพลย์ลิส", + "editText": "แก้ไข\nเพลย์ลิส", + "newText": "สร้าง\nเพลย์ลิสใหม่", + "showTutorialText": "โชว์การสอนก่อนเริ่ม", + "shuffleGameOrderText": "เรียงเกมตามลำดับ", + "titleText": "ปรับแต่งเพลย์ลิส ${TYPE}" + }, + "gameSettingsWindow": { + "addGameText": "เพิ่มเกม" + }, + "gamesToText": "ชนะ ${WINCOUNT} ต่อ ${LOSECOUNT}", + "gatherWindow": { + "aboutDescriptionLocalMultiplayerExtraText": "โปรดจำ: ไม่ว่าอุปกรณ์ใหนก็ตามในปาร์ตี้ก็สามารถมี\nมากกว่า 1 ผู้เล่นได้ถ้าหากคุณมีคอนโทรลเลอร์พอ", + "aboutDescriptionText": "ใช้แท็บนี้ในการจัดปาร์ตี้\n\nปาร์ตี้จะทำให้คุณเล่นเกมและทัวร์นาเมนท์\nกับเพื่อนของคุณผ่านอุปกรณ์ที่แตกต่างกันได้\n\nใช้ปุ่ม ${PARTY} ด้านบนขวาเพื่อสนทนาและ\nทำความรู้จักกับคนในปาร์ตี้ของคุณ\n(ในคอนโทรลเลอร์ ใช้ปุ่ม ${BUTTON} ในหน้าเมนู)", + "aboutText": "เกี่ยวกับ", + "addressFetchErrorText": "<พบข้อผิดพลาดในการหาแอดเดรส>", + "appInviteMessageText": "${NAME} ให้ตั๋วคุณ ${COUNT} อันใน ${APP_NAME}", + "appInviteSendACodeText": "ส่งรหัสให้กับพวกเขา", + "appInviteTitleText": "ชวนเพื่อน ${APP_NAME}", + "bluetoothAndroidSupportText": "(ทำงานร่วมกับเครื่องแอนดรอยด์ที่สนับสนุน Bluetooth)", + "bluetoothDescriptionText": "สร้าง/เข้าร่วมปาร์ตี้ผ่านทาง Bluetooth:", + "bluetoothHostText": "สร้างปาร์ตี้ผ่านทาง Bluetooth", + "bluetoothJoinText": "เข้าร่วมปาร์ตี้ผ่านทาง Bluetooth", + "bluetoothText": "บลูทูธ", + "checkingText": "กำลังตรวจ..", + "copyCodeConfirmText": "คัดลอกรหัสไปยังคลิปบอร์ดแล้ว", + "copyCodeText": "คัดลอกรหัส", + "dedicatedServerInfoText": "เพื่อผลลัพธ์ที่ดีที่สุดตั้งค่าเซิร์ฟเวอร์เฉพาะ ดู bombsquadgame.com/server เพื่อเรียนรู้วิธีการ", + "disconnectClientsText": "สิ่งนี้จะตัดการเชื่อมต่อผู้เล่น ${COUNT} คน\nในงานปาร์ตี้ของคุณ คุณแน่ใจไหม?", + "earnTicketsForRecommendingAmountText": "เพื่อน ๆ จะได้รับตั๋ว ${COUNT} ถ้าพวกเขาลองเล่นเกม\n(และคุณจะได้รับ ${YOU_COUNT} สำหรับแต่ละคนที่ทำ)", + "earnTicketsForRecommendingText": "แชร์เกมนี้\nสำหรับตั๋วฟรี", + "emailItText": "อีเมล์นี้", + "favoritesSaveText": "บันทึกเป็นรายการโปรด", + "favoritesText": "รายการโปรด", + "freeCloudServerAvailableMinutesText": "เซิร์ฟเวอร์คลาวด์ฟรีถัดไปพร้อมใช้งานใน ${MINUTES} นาที", + "freeCloudServerAvailableNowText": "เซิร์ฟเวอร์คลาวด์ฟรีพร้อมใช้งาน!", + "freeCloudServerNotAvailableText": "ไม่มีเซิร์ฟเวอร์คลาวด์ฟรี", + "friendHasSentPromoCodeText": "${COUNT} ${APP_NAME} ตั๋วจาก ${NAME}", + "friendPromoCodeAwardText": "คุณจะได้รับตั๋ว ${COUNT} ทุกครั้งที่ใช้", + "friendPromoCodeExpireText": "รหัสจะหมดอายุใน ${EXPIRE_HOURS} ชั่วโมงและใช้ได้กับผู้เล่นใหม่เท่านั้น", + "friendPromoCodeInstructionsText": "หากต้องการใช้งานให้เปิด ${APP_NAME} แล้วไปที่ \"การตั้งค่า -> ขั้นสูง -> ใส่รหัส\"\nดู bombsquadgame.com สำหรับลิงก์ดาวน์โหลดสำหรับแพลตฟอร์มที่รองรับทั้งหมด", + "friendPromoCodeRedeemLongText": "สามารถแลกเป็นตั๋วฟรี ${COUNT} คนสูงถึง ${MAX_USES} คน", + "friendPromoCodeRedeemShortText": "สามารถแลกเป็นตั๋ว ${COUNT} ใบในเกม", + "friendPromoCodeWhereToEnterText": "(ใน \"การตั้งค่า->ขั้นสูง->ป้อนรหัส\")", + "getFriendInviteCodeText": "รับรหัสเชิญเพื่อน", + "googlePlayDescriptionText": "เชิญผู้เล่น Google Play เข้าร่วมปาร์ตี้ของคุณ:", + "googlePlayInviteText": "เชิญ", + "googlePlayReInviteText": "มีผู้เล่น Google Play ${COUNT} คนในปาร์ตี้ของคุณ\nใครจะถูกตัดการเชื่อมต่อหากคุณเริ่มคำเชิญใหม่\nรวมไว้ในคำเชิญใหม่เพื่อรับพวกเขากลับมา", + "googlePlaySeeInvitesText": "ดูคำเชิญ", + "googlePlayText": "Google Play", + "googlePlayVersionOnlyText": "(เวอร์ชัน Android / Google Play)", + "hostPublicPartyDescriptionText": "โฮสต์ปาร์ตี้สาธารณะ", + "hostingUnavailableText": "โฮสต์ไม่พร้อมใช้งาน", + "inDevelopmentWarningText": "บันทึก:\n\nการเล่นผ่านเครือข่ายเป็นคุณลักษณะใหม่ที่ยังคงพัฒนาอยู่\nสำหรับตอนนี้ขอแนะนำเป็นอย่างยิ่งว่า\nผู้เล่นอยู่ในเครือข่าย Wi-Fi เดียวกัน", + "internetText": "อินเทอร์เน็ต", + "inviteAFriendText": "เพื่อนไม่มีเกม? ชวนเพื่อน\nมาลองแล้วจะได้รับตั๋วฟรี ${COUNT} ใบ", + "inviteFriendsText": "เชิญเพื่อน ๆ", + "joinPublicPartyDescriptionText": "เข้าร่วมปาร์ตี้สาธารณะ", + "localNetworkDescriptionText": "เข้าร่วมปาร์ตี้ใกล้เคียง (LAN, Bluetooth ฯลฯ)", + "localNetworkText": "เครือข่ายท้องถิ่น", + "makePartyPrivateText": "ทำให้ปาร์ตี้ของฉันเป็นส่วนตัว", + "makePartyPublicText": "ทำให้ปาร์ตี้ของฉันเป็นสาธารณะ", + "manualAddressText": "ที่อยู่", + "manualConnectText": "เชื่อมต่อ", + "manualDescriptionText": "เข้าร่วมปาร์ตี้ด้วยที่อยู่:", + "manualJoinSectionText": "เข้าร่วมด้วยที่อยู่", + "manualJoinableFromInternetText": "คุณสามารถเข้าร่วมจากอินเทอร์เน็ตได้หรือไม่:", + "manualJoinableNoWithAsteriskText": "ไม่*", + "manualJoinableYesText": "ใช่", + "manualRouterForwardingText": "*เพื่อแก้ไขปัญหานี้ ลองกำหนดค่าเราเตอร์ของคุณเพื่อส่งต่อพอร์ต UDP ${PORT} ไปยังที่อยู่ในเครื่องของคุณ", + "manualText": "ด้วยตนเอง", + "manualYourAddressFromInternetText": "ที่อยู่ของคุณจากอินเทอร์เน็ต:", + "manualYourLocalAddressText": "ที่อยู่ในท้องถิ่นของคุณ:", + "nearbyText": "ใกล้เคียง", + "noConnectionText": "<ไม่มีการเชื่อมต่อ>", + "otherVersionsText": "(เวอร์ชั่นอื่นๆ)", + "partyCodeText": "รหัสปาร์ตี้", + "partyInviteAcceptText": "ยอมรับ", + "partyInviteDeclineText": "ปฏิเสธ", + "partyInviteGooglePlayExtraText": "(ดูแท็บ 'Google Play' ในหน้าต่าง 'รวบรวม')", + "partyInviteIgnoreText": "ไม่สนใจ", + "partyInviteText": "${NAME} เชิญ\nคุณเข้าร่วมปาร์ตี้ของเขา!", + "partyNameText": "ชื่อปาร์ตี้", + "partyServerRunningText": "เซิร์ฟเวอร์ปาร์ตี้ของคุณกำลังทำงาน", + "partySizeText": "ขนาดปาร์ตี้", + "partyStatusCheckingText": "กำลังตรวจสอบสถานะ...", + "partyStatusJoinableText": "ปาร์ตี้ของคุณสามารถเข้าร่วมได้จากอินเทอร์เน็ต", + "partyStatusNoConnectionText": "ไม่สามารถเชื่อมต่อกับระบบได้", + "partyStatusNotJoinableText": "ปาร์ตี้ของคุณไม่สามารถเข้าร่วมจากอินเทอร์เน็ตได้", + "partyStatusNotPublicText": "ปาร์ตี้ของคุณไม่เป็นสาธารณะ", + "pingText": "ปิง", + "portText": "พอร์ต", + "privatePartyCloudDescriptionText": "ปาร์ตี้ส่วนตัวทำงานบนเซิร์ฟเวอร์คลาวด์เฉพาะ ไม่จำเป็นต้องกำหนดค่าเราเตอร์", + "privatePartyHostText": "โฮสต์ปาร์ตี้ส่วนตัว", + "privatePartyJoinText": "เข้าร่วมปาร์ตี้ส่วนตัว", + "privateText": "ส่วนตัว", + "publicHostRouterConfigText": "ซึ่งอาจต้องมีการกำหนดค่าการส่งต่อพอร์ตบนเราเตอร์ของคุณ ให้จัดปาร์ตี้ส่วนตัวเพื่อตัวเลือกที่ง่ายกว่า", + "publicText": "สาธารณะ", + "requestingAPromoCodeText": "ขอรหัส...", + "sendDirectInvitesText": "ส่งคำเชิญโดยตรง", + "shareThisCodeWithFriendsText": "แบ่งปันรหัสนี้กับเพื่อน:", + "showMyAddressText": "แสดงที่อยู่ของฉัน", + "startHostingPaidText": "โฮสต์ตอนนี้ราคา ${COST}", + "startHostingText": "โฮสต์", + "startStopHostingMinutesText": "คุณสามารถเริ่มและหยุดการโฮสต์ได้ฟรีเป็นเวลาอีก ${MINUTES} นาที", + "stopHostingText": "หยุดโฮสต์", + "titleText": "รวมตัว", + "wifiDirectDescriptionBottomText": "หากอุปกรณ์ทั้งหมดมีแผง 'Wi-Fi Direct' ก็ควรจะสามารถใช้แผงนี้เพื่อค้นหา\nและเชื่อมต่อถึงกัน เมื่อเชื่อมต่ออุปกรณ์ทั้งหมดแล้ว คุณสามารถสร้างปาร์ตี้ได้\nที่นี่โดยใช้แท็บ 'เครือข่ายท้องถิ่น' เช่นเดียวกับเครือข่าย Wi-Fi ปกติ\n\nเพื่อผลลัพธ์ที่ดีที่สุด โฮสต์ Wi-Fi Direct ควรเป็นโฮสต์ปาร์ตี้ ${APP_NAME} ด้วย", + "wifiDirectDescriptionTopText": "สามารถใช้ Wi-Fi Direct เพื่อเชื่อมต่ออุปกรณ์ Android ได้โดยตรงโดยไม่ต้องใช้\nต้องการเครือข่าย Wi-Fi วิธีนี้ใช้ได้ผลดีที่สุดบน Android 4.2 หรือใหม่กว่า\n\nในการใช้งาน ให้เปิดการตั้งค่า Wi-Fi และมองหา 'Wi-Fi Direct' ในเมนู", + "wifiDirectOpenWiFiSettingsText": "เปิดการตั้งค่า Wi-Fi", + "wifiDirectText": "Wi-Fi Direct", + "worksBetweenAllPlatformsText": "(ทำงานระหว่างแพลตฟอร์มทั้งหมด)", + "worksWithGooglePlayDevicesText": "(ใช้งานได้กับอุปกรณ์ที่ใช้เกมเวอร์ชัน Google Play (android))", + "youHaveBeenSentAPromoCodeText": "คุณได้รับรหัสโปรโหมด ${APP_NAME} แล้ว:" + }, + "getTicketsWindow": { + "freeText": "ฟรี!", + "freeTicketsText": "ตั๋วฟรี", + "inProgressText": "กำลังดำเนินการธุรกรรม โปรดลองอีกครั้งในอีกสักครู่", + "purchasesRestoredText": "คืนค่าการซื้อแล้ว", + "receivedTicketsText": "ได้รับตั๋ว ${COUNT} ใบแล้ว!", + "restorePurchasesText": "เรียกคืนการซื้อสินค้า", + "ticketPack1Text": "แพ็คตั๋วขนาดเล็ก", + "ticketPack2Text": "แพ็คตั๋วขนาดกลาง", + "ticketPack3Text": "แพ็กตั๋วขนาดใหญ่", + "ticketPack4Text": "แพ็กตั๋วจัมโบ้", + "ticketPack5Text": "แพ็กตั๋วแมมมอธ", + "ticketPack6Text": "แพ็กตั๋วสูงสุด", + "ticketsFromASponsorText": "รับตั๋ว ${COUNT} ใบ\nจากสปอนเซอร์", + "ticketsText": "ตั๋ว ${COUNT} ใบ", + "titleText": "รับตั๋ว", + "unavailableLinkAccountText": "ขออภัย ไม่สามารถซื้อได้บนแพลตฟอร์มนี้\nวิธีแก้ปัญหา คุณสามารถเชื่อมโยงบัญชีนี้กับบัญชีบน\nแพลตฟอร์มอื่นและทำการซื้อที่นั่น", + "unavailableTemporarilyText": "ไม่สามารถใช้งานได้ในขณะนี้ โปรดลองอีกครั้งในภายหลัง.", + "unavailableText": "ขออภัย ไม่สามารถใช้ได้", + "versionTooOldText": "ขออภัย เกมเวอร์ชันนี้เก่าเกินไป โปรดอัปเดตเป็นเวอร์ชันที่ใหม่กว่า", + "youHaveShortText": "คุณมี ${COUNT} ใบ", + "youHaveText": "คุณมีตั๋ว ${COUNT} ใบ" + }, + "googleMultiplayerDiscontinuedText": "ขออภัย บริการผู้เล่นหลายคนของ Google ไม่มีให้บริการอีกต่อไป\nฉันกำลังดำเนินการเปลี่ยนให้เร็วที่สุด\nในระหว่างนี้ โปรดลองวิธีการเชื่อมต่ออื่น\n-เอริค", + "googlePlayText": "Google Play", + "graphicsSettingsWindow": { + "alwaysText": "ตลอด", + "fullScreenCmdText": "เต็มหน้าจอ (Cmd-F)", + "fullScreenCtrlText": "เต็มหน้าจอ (Ctrl-F)", + "gammaText": "ส่องสว่าง", + "highText": "สูง", + "higherText": "สูงกว่า", + "lowText": "ต่ำ", + "mediumText": "ปานกลาง", + "neverText": "ไม่เคย", + "resolutionText": "ความละเอียด", + "showFPSText": "แสดง FPS", + "texturesText": "พื้นผิว", + "titleText": "กราฟิก", + "tvBorderText": "ขอบทีวี", + "verticalSyncText": "ซิงค์แนวตั้ง", + "visualsText": "ภาพ" + }, + "helpWindow": { + "bombInfoText": "- ระเบิด -\nแข็งแกร่งกว่าหมัด แต่\nอาจทำให้ตนเองบาดเจ็บสาหัสได้\nเพื่อผลลัพธ์ที่ดีที่สุด ให้โยนไปทาง\nศัตรูก่อนฟิวส์หมด", + "canHelpText": "${APP_NAME} ช่วยได้", + "controllersInfoText": "คุณสามารถเล่น ${APP_NAME} กับเพื่อนผ่านเครือข่ายหรือคุณ\nทุกคนสามารถเล่นบนอุปกรณ์เดียวกันได้ถ้าคุณมีตัวควบคุมเพียงพอ\n${APP_NAME} รองรับได้หลากหลาย; คุณยังสามารถใช้โทรศัพท์ได้\nในฐานะผู้ควบคุมผ่านแอป '${REMOTE_APP_NAME}' ฟรี\nดูการตั้งค่า -> ตัวควบคุมสำหรับข้อมูลเพิ่มเติม", + "controllersInfoTextRemoteOnly": "คุณสามารถเล่น ${APP_NAME} กับเพื่อนผ่านเครือข่ายหรือคุณ\nทุกคนสามารถเล่นบนอุปกรณ์เดียวกันได้โดยใช้โทรศัพท์เป็น\nคอนโทรลเลอร์ผ่านแอป '${REMOTE_APP_NAME}' ฟรี", + "controllersText": "คอนโทรลเลอร์", + "controlsSubtitleText": "ตัวละครที่เป็นมิตร ${APP_NAME} ของคุณมีการดำเนินการพื้นฐานบางประการ:", + "controlsText": "การควบคุม", + "devicesInfoText": "เวอร์ชัน VR ของ ${APP_NAME} สามารถเล่นผ่านเครือข่ายได้ด้วย\nเวอร์ชันปกติ ลองใช้โทรศัพท์ แท็บเล็ตเพิ่มเติม\nและคอมพิวเตอร์และเล่นเกมของคุณ มันยังมีประโยชน์กับ\nเชื่อมต่อเกมเวอร์ชันปกติกับเวอร์ชัน VR เพียงเพื่อ\nให้คนภายนอกได้ชมการกระทำ", + "devicesText": "อุปกรณ์", + "friendsGoodText": "สิ่งเหล่านี้เป็นสิ่งที่ดีที่จะมี ${APP_NAME} สนุกที่สุดด้วยหลายรายการ\nผู้เล่นและสามารถรองรับได้ถึง 8 ต่อครั้ง ซึ่งทำให้เรา:", + "friendsText": "เพื่อน", + "jumpInfoText": "- กระโดด -\nกระโดดข้ามช่องว่างเล็ก ๆ\nโยนสิ่งที่สูงขึ้นและ\nเพื่อแสดงความรู้สึกของความสุข", + "orPunchingSomethingText": "หรือต่อยอะไรบางอย่าง ขว้างมันออกจากหน้าผา แล้วเป่าขึ้นระหว่างทางด้วยระเบิดเหนียว", + "pickUpInfoText": "- ยกขึ้น -\nคว้าธง ศัตรู หรืออะไรก็ได้\nอย่างอื่นไม่ได้ยึดติดกับพื้น\nกดอีกครั้งเพื่อโยน", + "powerupBombDescriptionText": "ให้คุณโยนระเบิดสามลูก\nแทนที่จะเป็นลูกเดียว", + "powerupBombNameText": "ระเบิดสามลูก", + "powerupCurseDescriptionText": "คุณอาจต้องการหลีกเลี่ยงสิ่งเหล่านี้\n ...หรือคุณ?", + "powerupCurseNameText": "คำสาป", + "powerupHealthDescriptionText": "ให้คุณมีสุขภาพแข็งแรงสมบูรณ์\nคุณคงไม่เคยคิด", + "powerupHealthNameText": "แพ็คยา", + "powerupIceBombsDescriptionText": "อ่อนแอกว่าระเบิดธรรมดา\nแต่ปล่อยให้ศัตรูเย็นชา\nและเปราะเป็นพิเศษ", + "powerupIceBombsNameText": "ระเบิดน้ำแข็ง", + "powerupImpactBombsDescriptionText": "อ่อนกว่าปกติเล็กน้อย\nระเบิด แต่ระเบิดเมื่อกระทบ", + "powerupImpactBombsNameText": "ระเบิดไก", + "powerupLandMinesDescriptionText": "เจ้านี่มาในแพ็ค 3 อัน;\nมีประโยชน์สำหรับการป้องกันฐานหรือ\nหยุดศัตรูที่รวดเร็ว", + "powerupLandMinesNameText": "ทุ่นระเบิด", + "powerupPunchDescriptionText": "ทำให้หมัดของคุณหนักขึ้น\nเร็วขึ้นดีขึ้นแข็งแรงขึ้น", + "powerupPunchNameText": "นวมชกมวย", + "powerupShieldDescriptionText": "ดูดซับความเสียหายเล็กน้อย\nดังนั้นคุณไม่จำเป็นต้อง", + "powerupShieldNameText": "โล่พลังงาน", + "powerupStickyBombsDescriptionText": "ยึดติดกับสิ่งที่พวกเขาตี\nความฮาจึงบังเกิด", + "powerupStickyBombsNameText": "ระเบิดเหนียวหนืด", + "powerupsSubtitleText": "แน่นอนว่าไม่มีเกมใดที่จะสมบูรณ์ได้หากไม่มีการเพิ่มพลัง:", + "powerupsText": "เพิ่มพลัง", + "punchInfoText": "- ต่อย -\nหมัดสร้างความเสียหายมากขึ้น\nหมัดของคุณขยับเร็วขึ้นดังนั้น\nวิ่งและหมุนเหมือนคนบ้า", + "runInfoText": "- วิ่ง -\nกดค้างปุ่มใดก็ได้เพื่อวิ่ง ปุ่มทริกเกอร์หรือปุ่มไหล่ทำงานได้ดีถ้าคุณมี\nการวิ่งทำให้คุณได้ตำแหน่งเร็วขึ้นแต่ทำให้เลี้ยวยาก ดังนั้นให้ระวังหน้าผา", + "someDaysText": "บางวันคุณรู้สึกอยากต่อยอะไรบางอย่าง หรือเป่าอะไรขึ้น", + "titleText": "${APP_NAME} ความช่วยเหลือ", + "toGetTheMostText": "เพื่อให้ได้ประโยชน์สูงสุดจากเกมนี้ คุณจะต้อง:", + "welcomeText": "ยินดีต้อนรับสู่ ${APP_NAME}!" + }, + "holdAnyButtonText": "<กดปุ่มใดก็ได้>", + "holdAnyKeyText": "<กดปุ่มใดก็ได้>", + "hostIsNavigatingMenusText": "- ${HOST} กำลังนำทางเมนูเหมือนหัวหน้า -", + "importPlaylistCodeInstructionsText": "ใช้รหัสต่อไปนี้เพื่อนำเข้าเพลย์ลิสต์นี้ที่อื่น:", + "importPlaylistSuccessText": "นำเข้าเพลย์ลิสต์ ${TYPE} '${NAME}'", + "importText": "นำเข้า", + "importingText": "กำลังนำเข้า...", + "inGameClippedNameText": "ในเกมจะเป็น\n\"${NAME}\"", + "installDiskSpaceErrorText": "ข้อผิดพลาด: ไม่สามารถทำการติดตั้งให้เสร็จสิ้นได้\nคุณอาจไม่มีที่ว่างบนอุปกรณ์ของคุณ\nล้างพื้นที่บางส่วนแล้วลองอีกครั้ง", + "internal": { + "arrowsToExitListText": "กด ${LEFT} หรือ ${RIGHT} เพื่อออกจากรายการ", + "buttonText": "ปุ่ม", + "cantKickHostError": "คุณเตะโฮสต์ไม่ได้", + "chatBlockedText": "${NAME} ถูกบล็อกการแชทเป็นเวลา ${TIME} วินาที", + "connectedToGameText": "เข้าร่วม '${NAME}'", + "connectedToPartyText": "เข้าร่วมปาร์ตี้ของ ${NAME}!", + "connectingToPartyText": "กำลังเชื่อมต่อ...", + "connectionFailedHostAlreadyInPartyText": "การเชื่อมต่อล้มเหลว; โฮสต์อยู่ในปาร์ตี้อื่น", + "connectionFailedPartyFullText": "การเชื่อมต่อล้มเหลว; ปาร์ตี้เต็ม", + "connectionFailedText": "การเชื่อมต่อล้มเหลว.", + "connectionFailedVersionMismatchText": "การเชื่อมต่อล้มเหลว; โฮสต์กำลังใช้เกมเวอร์ชันอื่น\nตรวจสอบให้แน่ใจว่าคุณเป็นเวอร์ชั่นล่าสุดแล้วลองอีกครั้ง", + "connectionRejectedText": "การเชื่อมต่อถูกปฏิเสธ", + "controllerConnectedText": "เชื่อมต่อ ${CONTROLLER} แล้ว", + "controllerDetectedText": "ตรวจพบคอนโทรลเลอร์ 1 รายการ", + "controllerDisconnectedText": "${CONTROLLER} ถูกตัดการเชื่อมต่อ", + "controllerDisconnectedTryAgainText": "${CONTROLLER} ถูกตัดการเชื่อมต่อ โปรดลองเชื่อมต่ออีกครั้ง", + "controllerForMenusOnlyText": "คอนโทรลเลอร์นี้ไม่สามารถใช้เล่นได้ ใช้เพื่อนำทางไปยังเมนูเท่านั้น", + "controllerReconnectedText": "${CONTROLLER} เชื่อมต่อใหม่แล้ว", + "controllersConnectedText": "เชื่อมต่อตัวควบคุม ${COUNT} รายการแล้ว", + "controllersDetectedText": "ตรวจพบคอนโทรลเลอร์ ${COUNT} รายการ", + "controllersDisconnectedText": "ยกเลิกการเชื่อมต่อคอนโทรลเลอร์ ${COUNT} รายการแล้ว", + "corruptFileText": "ตรวจพบไฟล์ที่เสียหาย โปรดลองติดตั้งใหม่ หรือส่งอีเมลไปที่ ${EMAIL}", + "errorPlayingMusicText": "เกิดข้อผิดพลาดในการเล่นเพลง: ${MUSIC}", + "errorResettingAchievementsText": "ไม่สามารถรีเซ็ตความสำเร็จออนไลน์ได้ โปรดลองอีกครั้งในภายหลัง.", + "hasMenuControlText": "${NAME} มีการควบคุมเมนู", + "incompatibleNewerVersionHostText": "โฮสต์กำลังเรียกใช้เกมเวอร์ชันใหม่กว่า\nอัปเดตเป็นเวอร์ชันล่าสุดแล้วลองอีกครั้ง", + "incompatibleVersionHostText": "โฮสต์กำลังเรียกใช้เกมเวอร์ชันอื่น\nตรวจสอบให้แน่ใจว่าคุณเป็นข้อมูลล่าสุดแล้วลองอีกครั้ง", + "incompatibleVersionPlayerText": "${NAME} กำลังเรียกใช้เกมเวอร์ชันอื่น\nตรวจสอบให้แน่ใจว่าคุณเป็นข้อมูลล่าสุดแล้วลองอีกครั้ง", + "invalidAddressErrorText": "ข้อผิดพลาด: ที่อยู่ไม่ถูกต้อง", + "invalidNameErrorText": "ข้อผิดพลาด: ชื่อไม่ถูกต้อง", + "invalidPortErrorText": "ข้อผิดพลาด: พอร์ตไม่ถูกต้อง", + "invitationSentText": "ส่งคำเชิญแล้ว", + "invitationsSentText": "ส่งคำเชิญแล้ว ${COUNT} รายการ", + "joinedPartyInstructionsText": "มีคนเข้าร่วมปาร์ตี้ของคุณ\nไปที่ 'เล่น' เพื่อเริ่มเกม", + "keyboardText": "แป้นพิมพ์", + "kickIdlePlayersKickedText": "เตะ ${NAME} เพราะไม่ได้ใช้งาน", + "kickIdlePlayersWarning1Text": "${NAME} จะถูกเตะใน ${COUNT} วินาทีหากไม่ได้ใช้งาน", + "kickIdlePlayersWarning2Text": "(คุณสามารถปิดได้ในการตั้งค่า -> ขั้นสูง)", + "leftGameText": "ออก '${NAME}'", + "leftPartyText": "ออกจากปาร์ตี้ของ ${NAME}", + "noMusicFilesInFolderText": "โฟลเดอร์ไม่มีไฟล์เพลง", + "playerJoinedPartyText": "${NAME} เข้าร่วมปาร์ตี้!", + "playerLeftPartyText": "${NAME} ออกจากปาร์ตี้", + "rejectingInviteAlreadyInPartyText": "ปฏิเสธคำเชิญ (อยู่ในปาร์ตี้แล้ว)", + "serverRestartingText": "เซิร์ฟเวอร์กำลังเริ่มต้นใหม่ โปรดเข้าร่วมใหม่ในอีกสักครู่...", + "serverShuttingDownText": "กำลังปิดเซิฟเวอร์...", + "signInErrorText": "เกิดข้อผิดพลาดในการลงชื่อเข้าใช้", + "signInNoConnectionText": "ไม่สามารถลงชื่อเข้าใช้ (ไม่มีการเชื่อมต่ออินเทอร์เน็ตหรอ?)", + "telnetAccessDeniedText": "ข้อผิดพลาด: ผู้ใช้ไม่ได้ให้สิทธิ์การเข้าถึง telnet", + "timeOutText": "(หมดเวลาใน ${TIME} วินาที)", + "touchScreenJoinWarningText": "คุณได้เข้าร่วมกับหน้าจอสัมผัส\nหากนี่เป็นข้อผิดพลาด ให้แตะ 'เมนู -> ออกจากเกม' ด้วย", + "touchScreenText": "หน้าจอสัมผัส", + "unableToResolveHostText": "ข้อผิดพลาด: ไม่สามารถแก้ไขโฮสต์", + "unavailableNoConnectionText": "ไม่สามารถใช้งานได้ในขณะนี้ (ไม่มีการเชื่อมต่ออินเทอร์เน็ตหรอ?)", + "vrOrientationResetCardboardText": "ใช้เพื่อรีเซ็ตการวางแนว VR\nในการเล่นเกม คุณจะต้องมีคอนโทรลเลอร์ภายนอก", + "vrOrientationResetText": "รีเซ็ตการวางแนว VR", + "willTimeOutText": "(จะหมดเวลาถ้าว่าง)" + }, + "jumpBoldText": "กระโดด", + "jumpText": "กระโดด", + "keepText": "เก็บไว้", + "keepTheseSettingsText": "เก็บการตั้งค่าเหล่านี้ไว้ไหม", + "keyboardChangeInstructionsText": "กด Space สองครั้งเพื่อเปลี่ยนคีย์บอร์ด", + "keyboardNoOthersAvailableText": "ไม่มีแป้นพิมพ์อื่นๆ", + "keyboardSwitchText": "กำลังสลับแป้นพิมพ์เป็น \"${NAME}\"", + "kickOccurredText": "${NAME} ถูกเตะ", + "kickQuestionText": "เตะ ${NAME} ไหม?", + "kickText": "เตะ", + "kickVoteCantKickAdminsText": "แอดมินไม่สามารถเตะได้", + "kickVoteCantKickSelfText": "คุณไม่สามารถเตะตัวเองได้", + "kickVoteFailedNotEnoughVotersText": "ผู้เล่นไม่เพียงพอสำหรับการโหวท", + "kickVoteFailedText": "โหวตเตะล้มเหลว", + "kickVoteStartedText": "เริ่มต้นการโหวตสำหรับ ${NAME} แล้ว", + "kickVoteText": "โหวตเตะ", + "kickVotingDisabledText": "โหวทเตะ ถูกปิดใช้งาน", + "kickWithChatText": "พิมพ์ ${YES} ในแชทถ้าโหวตใช่และพิมพ์ ${NO} ถ้าโหวตไม่", + "killsTallyText": "ฆ่าไป ${COUNT}", + "killsText": "ฆ่า", + "kioskWindow": { + "easyText": "ง่าย", + "epicModeText": "โหมดมหากาพย์", + "fullMenuText": "เมนูเต็ม", + "hardText": "ยาก", + "mediumText": "ปานกลาง", + "singlePlayerExamplesText": "ตัวอย่างผู้เล่นคนเดียว / Co-op", + "versusExamplesText": "ตัวอย่างต่อสู้" + }, + "languageSetText": "ขณะนี้ภาษาคือ \"${LANGUAGE}\"", + "lapNumberText": "รอบ ${CURRENT}/${TOTAL}", + "lastGamesText": "(${COUNT} เกมสุดท้าย )", + "leaderboardsText": "กระดานผู้นำ", + "league": { + "allTimeText": "ตลอดเวลา", + "currentSeasonText": "ซีซั่นปัจจุบัน (${NUMBER})", + "leagueFullText": "ลีก ${NAME}", + "leagueRankText": "อันดับลีก", + "leagueText": "ลีก", + "rankInLeagueText": "#${RANK} ลีก ${NAME} ${SUFFIX}", + "seasonEndedDaysAgoText": "สิ้นสุดซีซั่นเมื่อ ${NUMBER} วันที่ผ่านมา", + "seasonEndsDaysText": "จะสิ้นสุดซีซั่นในอีก ${NUMBER} วัน", + "seasonEndsHoursText": "จะสิ้นสุดซีซั่นในอีก ${NUMBER} ชั่วโมง", + "seasonEndsMinutesText": "จะสิ้นสุดซีซั่นในอีก ${NUMBER} นาที", + "seasonText": "ซีซั่น ${NUMBER}", + "tournamentLeagueText": "คุณต้องไปถึงลีก ${NAME} เพื่อเข้าร่วมการแข่งขันนี้", + "trophyCountsResetText": "จำนวนถ้วยรางวัลจะรีเซ็ตในซีซั่นหน้า" + }, + "levelBestScoresText": "คะแนนที่ดีที่สุดใน ${LEVEL}", + "levelBestTimesText": "เวลาที่ดีที่สุดใน ${LEVEL}", + "levelIsLockedText": "${LEVEL} ถูกล็อก", + "levelMustBeCompletedFirstText": "${LEVEL} ต้องเสร็จสิ้นก่อน", + "levelText": "ระดับ ${NUMBER}", + "levelUnlockedText": "ปลดล็อคระดับ!", + "livesBonusText": "โบนัสชีวิต", + "loadingText": "กำลังโหลด", + "loadingTryAgainText": "กำลังโหลด; โปรดลองอีกครั้งในอีกสักครู่...", + "macControllerSubsystemBothText": "ทั้งสอง (ไม่แนะนำ)", + "macControllerSubsystemClassicText": "คลาสสิค", + "macControllerSubsystemDescriptionText": "(ลองเปลี่ยนสิ่งนี้หากคอนโทรลเลอร์ของคุณไม่ทำงาน)", + "macControllerSubsystemMFiNoteText": "ตรวจพบคอลโทรลเลอร์ที่สร้างขึ้นสำหรับ iOS/Mac\nคุณอาจต้องการเปิดใช้งานสิ่งเหล่านี้ในการตั้งค่า -> โทรลเลอร์", + "macControllerSubsystemMFiText": "สร้างขึ้นสำหรับ iOS/Mac", + "macControllerSubsystemTitleText": "รองรับคอนโทรลเลอร์", + "mainMenu": { + "creditsText": "เครดิต", + "demoMenuText": "เมนูสาธิต", + "endGameText": "จบเกม", + "exitGameText": "ออกจากเกม", + "exitToMenuText": "จะออกไปหน้าเมนูหรือไม่?", + "howToPlayText": "วิธีการเล่น", + "justPlayerText": "(แค่ ${NAME})", + "leaveGameText": "ออกจากเกม", + "leavePartyConfirmText": "คุณแน่ใจที่จะออกจากปาร์ตี้หรือไม่?", + "leavePartyText": "ออกจากปาร์ตี้", + "quitText": "ออก", + "resumeText": "เล่นต่อ", + "settingsText": "ตั้งค่า" + }, + "makeItSoText": "ใช้", + "mapSelectGetMoreMapsText": "รับแผนที่เพิ่มเติม", + "mapSelectText": "เลือก...", + "mapSelectTitleText": "แผนที่สำหรับเกม ${GAME}", + "mapText": "แผนที่", + "maxConnectionsText": "จำนวนผู้ที่เชื่อมต่อได้สูงสุด", + "maxPartySizeText": "จำนวนคนในปาร์ตี้สูงสุด", + "maxPlayersText": "ผู้เล่นที่เล่นได้สูงสุด", + "modeArcadeText": "โหมดอาเขต", + "modeClassicText": "โหมดคลาสสิก", + "modeDemoText": "โหมดสาธิต", + "mostValuablePlayerText": "ผู้เล่นที่ทำคะแนนมากที่สุด", + "mostViolatedPlayerText": "ผู้เล่นที่ฆ่าน้อยที่สุด", + "mostViolentPlayerText": "ผู้เล่นที่ฆ่ามากที่สุด", + "moveText": "เคลื่อนไหว", + "multiKillText": "ฆ่า ${COUNT}!!!", + "multiPlayerCountText": "ผู้เล่น ${COUNT} คน", + "mustInviteFriendsText": "หมายเหตุ: คุณต้องเชิญเพื่อนใน\nแผง \"${GATHER}\" หรือไฟล์แนบ\nตัวควบคุมเพื่อเล่นหลายคน", + "nameBetrayedText": "${NAME} หักหลัง ${VICTIM}", + "nameDiedText": "${NAME} ตาย", + "nameKilledText": "${NAME} ได้ฆ่า ${VICTIM}", + "nameNotEmptyText": "ชื่อไม่สามารถปล่อยให้ว่างได้!", + "nameScoresText": "${NAME} ทำคะแนน!", + "nameSuicideKidFriendlyText": "${NAME} ตายจากอุบัติเหตุ", + "nameSuicideText": "${NAME} ฆ่าตัวตาย", + "nameText": "ชื่อ", + "nativeText": "พื้นเมือง", + "newPersonalBestText": "ใหม่ส่วนตัวดีที่สุด!", + "newTestBuildAvailableText": "มีการสร้างการทดสอบที่ใหม่กว่า! (${VERSION} บิลด์ ${BUILD})\nรับได้ที่ ${ADDRESS}", + "newText": "ใหม่", + "newVersionAvailableText": "มี ${APP_NAME} เวอร์ชันใหม่แล้ว! (${VERSION})", + "nextAchievementsText": "ความสำเร็จต่อไป:", + "nextLevelText": "ด่านต่อไป", + "noAchievementsRemainingText": "- ไม่มี", + "noContinuesText": "(ไม่มีต่อ)", + "noExternalStorageErrorText": "ไม่พบที่จัดเก็บข้อมูลภายนอกในอุปกรณ์นี้", + "noGameCircleText": "ข้อผิดพลาด: ไม่ได้ลงชื่อเข้าใช้ GameCircle", + "noScoresYetText": "ไม่มีคะแนน", + "noThanksText": "ไม่เป็นไรขอบคุณ", + "noTournamentsInTestBuildText": "คำเตือน: คะแนนการแข่งขันจากรุ่นทดสอบนี้จะถูกละเว้น", + "noValidMapsErrorText": "ไม่พบแผนที่ที่ถูกต้องสำหรับเกมนี้", + "notEnoughPlayersRemainingText": "ไม่มีผู้เล่นเหลือพอ กรุณาออกแล้วเริ่มเกมใหม่", + "notEnoughPlayersText": "คุณต้องการผู้เล่นอย่างน้อย ${COUNT} คนจึงจะเริ่มเกมได้!", + "notNowText": "ไม่ใช่ตอนนี้", + "notSignedInErrorText": "คุณต้องลงชื่อเข้าใช้เพื่อทำสิ่งนี้", + "notSignedInGooglePlayErrorText": "คุณต้องลงชื่อเข้าใช้ Google Play เพื่อดำเนินการนี้", + "notSignedInText": "ไม่ได้ลงชื่อเข้าใช้", + "nothingIsSelectedErrorText": "ไม่มีอะไรถูกเลือก!", + "numberText": "#${NUMBER}", + "offText": "ปิด", + "okText": "โอเค", + "onText": "เปิด", + "oneMomentText": "แป๊บนึง...", + "onslaughtRespawnText": "${PLAYER} จะเกิดใหม่ในรอบที่ ${WAVE}", + "orText": "${A} หรือ ${B}", + "otherText": "อื่น...", + "outOfText": "(#${RANK} จาก ${ALL})", + "ownFlagAtYourBaseWarning": "ธงของคุณจะต้องอยู่ที่\nฐานจึงจะทำคะแนนได้!", + "packageModsEnabledErrorText": "การเล่นแบบใช้เน็ตไม่มสามรถใช้งานได้ในเมื่อ \"แพ็คเกจม็อดท้องถิ่น\" กำลังเปิดอยู่ (ดูได้ใน การตั้งค่า-> ขั้นสูง)", + "partyWindow": { + "chatMessageText": "ข้อความแชท", + "emptyText": "ปาร์ตี้ของคุณว่าง", + "hostText": "(หัวหน้าห้อง)", + "sendText": "ส่ง", + "titleText": "ปาร์ตี้ของคุณ" + }, + "pausedByHostText": "(หยุดโดยเจ้าของห้อง)", + "perfectWaveText": "สมบูรณ์แบบ!", + "pickUpText": "หยิบขึ้นมา", + "playModes": { + "coopText": "ร่วมมือกัน", + "freeForAllText": "เล่นแบบอิสระ", + "multiTeamText": "เล่นแบบทีม", + "singlePlayerCoopText": "เล่นคนเดียวหรือแบบร่วมมือกัน", + "teamsText": "เล่นแบบทีม" + }, + "playText": "เล่น", + "playWindow": { + "oneToFourPlayersText": "1-4 คน", + "titleText": "เล่น", + "twoToEightPlayersText": "2-8 คน" + }, + "playerCountAbbreviatedText": "${COUNT}p", + "playerDelayedJoinText": "${PLAYER} จะเข้าร่วมเกมในรอบต่อไป", + "playerInfoText": "ข้อมูลผู้เล่น", + "playerLeftText": "${PLAYER} ออกจากเกม", + "playerLimitReachedText": "ผู้เล่นเต็ม ${COUNT} คนแล้ว ไม่สามารถเข้าร่วมได้", + "playerProfilesWindow": { + "cantDeleteAccountProfileText": "คุณไม่สามารถลบโปรไฟล์ของบัญชีได้", + "deleteButtonText": "ลบ\nโปรไฟล์", + "deleteConfirmText": "คุณแน่ใจที่จะลบ '${PROFILE}' หรือไม่?", + "editButtonText": "แก้ไข\nโปรไฟล์", + "explanationText": "(กำหนดชื่อและตัวละครเองสำหรับผู้เล่นในบัญชีนี้)", + "newButtonText": "สร้าง\nโปรไฟล์ใหม่", + "titleText": "โปรไฟล์ผู้เล่น" + }, + "playerText": "ผู้เล่น", + "playlistNoValidGamesErrorText": "เพลย์ลิสนี้ไม่ได้ใส่เกมที่ปลดล็อคแล้วเลย", + "playlistNotFoundText": "ไม่พบเพลย์ลิส", + "playlistText": "เพลย์ลิสต์", + "playlistsText": "เพลย์ลิส", + "pleaseRateText": "หากคุณชอบ ${APP_NAME} โปรดพิจารณาใช้ a\nและให้คะแนนหรือเขียนรีวิว นี้ให้\nข้อเสนอแนะที่เป็นประโยชน์และช่วยสนับสนุนการพัฒนาในอนาคต\n\nขอบใจ!\n-eric", + "pleaseWaitText": "โปรดรอ...", + "pluginsDetectedText": "ตรวจพบปลั๊กอินใหม่ เปิด/กำหนดค่าได้ในการตั้งค่า", + "pluginsText": "ปลั๊กอิน", + "practiceText": "ฝึกฝน", + "pressAnyButtonPlayAgainText": "กดปุ่มใดก็ได้เพื่อเล่นอีกครั้ง...", + "pressAnyButtonText": "กดปุ่มใดก็ได้เพื่อไปต่อ...", + "pressAnyButtonToJoinText": "กดปุ่มใดก็ได้เพื่อเข้าร่วม...", + "pressAnyKeyButtonPlayAgainText": "กดปุ่มใดก็ได้เพื่อเล่นอีกครั้ง...", + "pressAnyKeyButtonText": "กดปุ่มใดๆก็ได้ เพื่อดำเนินการต่อ...", + "pressAnyKeyText": "กดปุ่มใดก็ได้...", + "pressJumpToFlyText": "**กดกระโดดซ้ำๆเพื่อโบยบิน**", + "pressPunchToJoinText": "กด ต่อย เพื่อเข้าร่วม...", + "pressToOverrideCharacterText": "กด ${BUTTONS} เพื่อแทนที่ตัวละครของคุณ", + "pressToSelectProfileText": "กด ${BUTTONS} เพื่อเลือกผู้เล่น", + "pressToSelectTeamText": "กด ${BUTTONS} เพื่อเลือกทีม", + "promoCodeWindow": { + "codeText": "รหัส", + "enterText": "ถัดไป" + }, + "promoSubmitErrorText": "เกิดข้อผิดพลาดในการส่งรหัส ตรวจสอบการเชื่อมต่ออินเทอร์เน็ตของคุณ", + "ps3ControllersWindow": { + "macInstructionsText": "ปิดสวิตช์ที่ด้านหลัง PS3 ของคุณ ตรวจสอบให้แน่ใจ\nMac ของคุณเปิดใช้งาน Bluetooth แล้ว จากนั้นเชื่อมต่อคอนโทรลเลอร์ของคุณ\nกับ Mac ของคุณผ่านสาย USB เพื่อจับคู่ทั้งสองเครื่อง ตั้งแต่นั้นมา คุณ\nสามารถใช้ปุ่มโฮมของคอนโทรลเลอร์เพื่อเชื่อมต่อกับ Mac ของคุณ\nในโหมดมีสาย (USB) หรือไร้สาย (Bluetooth)\n\nใน Mac บางเครื่อง คุณอาจได้รับแจ้งให้ป้อนรหัสผ่านเมื่อจับคู่\nหากเกิดเหตุการณ์นี้ โปรดดูบทช่วยสอนต่อไปนี้หรือ google เพื่อขอความช่วยเหลือ\n\n\n\n\nคอนโทรลเลอร์ PS3 ที่เชื่อมต่อแบบไร้สายควรปรากฏขึ้นในอุปกรณ์\nรายการใน System Preferences->Bluetooth คุณอาจต้องลบออก\nจากรายการนั้นเมื่อคุณต้องการใช้กับ PS3 ของคุณอีกครั้ง\n\nตรวจสอบให้แน่ใจว่าได้ยกเลิกการเชื่อมต่อจาก Bluetooth เมื่อไม่อยู่ใน\nใช้งานไม่เช่นนั้นแบตเตอรี่จะสิ้นเปลืองต่อไป\n\nBluetooth ควรรองรับอุปกรณ์ที่เชื่อมต่อถึง 7 เครื่อง\nแม้ว่าระยะทางของคุณอาจแตกต่างกันไป", + "ouyaInstructionsText": "ในการใช้คอนโทรลเลอร์ PS3 กับ OUYA ของคุณ เพียงแค่เชื่อมต่อด้วยสาย USB\nครั้งเดียวเพื่อจับคู่ การทำเช่นนี้อาจตัดการเชื่อมต่อคอนโทรลเลอร์อื่นของคุณ ดังนั้น\nจากนั้นคุณควรรีสตาร์ท OUYA และถอดสาย USB\n\nจากนั้นคุณควรจะสามารถใช้ปุ่ม HOME ของคอนโทรลเลอร์เพื่อ\nเชื่อมต่อแบบไร้สาย เมื่อคุณเล่นเสร็จแล้ว ให้กดปุ่ม HOME ค้างไว้\nเป็นเวลา 10 วินาทีเพื่อปิดคอนโทรลเลอร์ มิฉะนั้นอาจยังคงอยู่ใน\nและเปลืองแบตเตอรี่", + "pairingTutorialText": "วิดีโอแนะนำการจับคู่", + "titleText": "การใช้คอนโทรลเลอร์ PS3 กับ ${APP_NAME}:" + }, + "punchBoldText": "ต่อย", + "punchText": "ต่อย", + "purchaseForText": "ซื้อในราคา ${PRICE}", + "purchaseGameText": "ซื้อเกม", + "purchasingText": "กำลังจัดซื้อ...", + "quitGameText": "ออกจาก ${APP_NAME} ไหม?", + "quittingIn5SecondsText": "ออกจากระบบใน 5 วินาที...", + "randomPlayerNamesText": "ชื่อเริ่มต้น", + "randomText": "สุ่ม", + "rankText": "อันดับ", + "ratingText": "เรตติ้ง", + "reachWave2Text": "ไปถึงเวฟ 2 เพื่อจัดอันดับ", + "readyText": "พร้อม", + "recentText": "ล่าสุด", + "remoteAppInfoShortText": "${APP_NAME} จะสนุกที่สุดเมื่อเล่นกับครอบครัวและเพื่อนๆ\nเชื่อมต่อคอนโทรลเลอร์ฮาร์ดแวร์อย่างน้อยหนึ่งตัวหรือติดตั้ง\nแอป ${REMOTE_APP_NAME} บนโทรศัพท์หรือแท็บเล็ตเพื่อใช้งาน\nเป็นคอนโทรลเลอร์", + "remote_app": { + "app_name": "BombSquad Remote", + "app_name_short": "BSRemote", + "button_position": "ตำแหน่งปุ่ม", + "button_size": "ขนาดปุ่ม", + "cant_resolve_host": "ไม่สามารถแก้ไขโฮสต์", + "capturing": "กำลังจับภาพ...", + "connected": "เชื่อมต่อแล้ว", + "description": "ใช้โทรศัพท์หรือแท็บเล็ตของคุณเป็นตัวควบคุมด้วย BombSquad\nสามารถเชื่อมต่ออุปกรณ์สูงสุด 8 เครื่องพร้อมกันเพื่อความคลั่งไคล้ผู้เล่นหลายคนในท้องถิ่นบนทีวีหรือแท็บเล็ตเครื่องเดียว", + "disconnected": "ตัดการเชื่อมต่อโดยเซิร์ฟเวอร์", + "dpad_fixed": "แก้ไขแล้ว", + "dpad_floating": "ลอยตัว", + "dpad_position": "ตำแหน่ง D-Pad", + "dpad_size": "ขนาด D-Pad", + "dpad_type": "ประเภท D-Pad", + "enter_an_address": "ใส่ที่อยู่", + "game_full": "เกมเต็มหรือไม่ยอมรับการเชื่อมต่อ", + "game_shut_down": "เกมปิดตัวลงแล้ว", + "hardware_buttons": "ปุ่มฮาร์ดแวร์", + "join_by_address": "เข้าร่วมตามที่อยู่…", + "lag": "ล่าช้า: ${SECONDS} วินาที", + "reset": "รีเซ็ตเป็นค่าเริ่มต้น", + "run1": "วิ่ง 1", + "run2": "วิ่ง 2", + "searching": "กำลังค้นหาเกม BombSquad...", + "searching_caption": "แตะที่ชื่อเกมเพื่อเข้าร่วม\nตรวจสอบให้แน่ใจว่าคุณอยู่ในเครือข่าย wifi เดียวกันกับเกม", + "start": "เริ่ม", + "version_mismatch": "เวอร์ชั่นตรงกัน.\nตรวจสอบให้แน่ใจว่า BombSquad และ BombSquad Remote\nเป็นเวอร์ชันล่าสุดแล้วลองอีกครั้ง" + }, + "removeInGameAdsText": "ปลดล็อก \"${PRO}\" ในร้านค้าเพื่อลบโฆษณาในเกม", + "renameText": "เปลี่ยนชื่อ", + "replayEndText": "สิ้นสุดการเล่นซ้ำ", + "replayNameDefaultText": "รีเพลย์เกมล่าสุด", + "replayReadErrorText": "เกิดข้อผิดพลาดในการอ่านไฟล์เล่นซ้ำ", + "replayRenameWarningText": "เปลี่ยนชื่อ \"${REPLAY}\" หลังเกมหากคุณต้องการเก็บไว้ มิฉะนั้นจะถูกเขียนทับ", + "replayVersionErrorText": "ขออภัย เล่นซ้ำนี้ทำในรูปแบบอื่น\nเวอร์ชั่นเกมแล้วใช้ไม่ได้", + "replayWatchText": "ดูรีเพลย์", + "replayWriteErrorText": "เกิดข้อผิดพลาดในการเขียนไฟล์เล่นซ้ำ", + "replaysText": "เล่นซ้ำ", + "reportPlayerExplanationText": "ใช้อีเมลนี้เพื่อรายงานการโกง ภาษาที่ไม่เหมาะสม หรือพฤติกรรมที่ไม่ดีอื่นๆ\nโปรดอธิบายด้านล่าง:", + "reportThisPlayerCheatingText": "โกง", + "reportThisPlayerLanguageText": "ภาษาที่ไม่เหมาะสม", + "reportThisPlayerReasonText": "คุณต้องการรายงานอะไร", + "reportThisPlayerText": "รายงานผู้เล่นคนนี้", + "requestingText": "กำลังขอ...", + "restartText": "เริ่มต้นใหม่", + "retryText": "ลองอีกครั้ง", + "revertText": "ย้อนกลับ", + "runText": "วิ่ง", + "saveText": "บันทึก", + "scanScriptsErrorText": "ข้อผิดพลาดในการสแกนสคริปต์; ดูบันทึกสำหรับรายละเอียด", + "scoreChallengesText": "คะแนนความท้าทาย", + "scoreListUnavailableText": "ไม่มีรายการคะแนน", + "scoreText": "คะแนน", + "scoreUnits": { + "millisecondsText": "มิลลิวินาที", + "pointsText": "คะแนน", + "secondsText": "วินาที" + }, + "scoreWasText": "(เดิมคือ ${COUNT})", + "selectText": "เลือก", + "seriesWinLine1PlayerText": "ชนะ", + "seriesWinLine1TeamText": "ชนะ", + "seriesWinLine1Text": "ชนะ", + "seriesWinLine2Text": "ซีรีส์", + "settingsWindow": { + "accountText": "บัญชี", + "advancedText": "ขั้นสูง", + "audioText": "เสียง", + "controllersText": "คอนโทรลเลอร์", + "graphicsText": "กราฟิก", + "playerProfilesMovedText": "หมายเหตุ: โปรไฟล์ผู้เล่นได้ย้ายไปที่หน้าต่างบัญชีในเมนูหลัก", + "titleText": "ตั้งค่า" + }, + "settingsWindowAdvanced": { + "alwaysUseInternalKeyboardDescriptionText": "(แป้นพิมพ์บนหน้าจอที่เรียบง่ายและเป็นมิตรกับตัวควบคุมสำหรับการแก้ไขข้อความ)", + "alwaysUseInternalKeyboardText": "ใช้แป้นพิมพ์ภายในเสมอ", + "benchmarksText": "เกณฑ์มาตรฐานและการทดสอบความเครียด", + "disableCameraGyroscopeMotionText": "ปิดใช้งานการเคลื่อนไหวของกล้อง Gyroscope", + "disableCameraShakeText": "ปิดใช้งานการสั่นของกล้อง", + "disableThisNotice": "(คุณสามารถปิดการใช้งานประกาศนี้ในการตั้งค่าขั้นสูง)", + "enablePackageModsDescriptionText": "(การเปิดใช้งานที่ทำให้ใช้ม็อดได้หลากหลายมากขึ้นแต่จะปิดระบบการเล่นแบบใช้เน็ต)", + "enablePackageModsText": "เปิดแพ็คเกจม็อดท้องถิ่น", + "enterPromoCodeText": "ใส่รหัส", + "forTestingText": "หมายเหตุ: ค่าเหล่านี้ใช้สำหรับการทดสอบเท่านั้นและจะสูญหายไปเมื่อออกจากแอป", + "helpTranslateText": "การแปลที่ไม่ใช่ภาษาอังกฤษของ ${APP_NAME} เป็นชุมชน\nสนับสนุนความพยายาม หากคุณต้องการมีส่วนร่วมหรือแก้ไข\nแปลตามลิงค์ด้านล่างครับ ขอบคุณล่วงหน้า!", + "kickIdlePlayersText": "เตะผู้เล่นที่ไม่ได้ใช้งาน", + "kidFriendlyModeText": "โหมดเป็นมิตรกับเด็ก (ลดความรุนแรง ฯลฯ)", + "languageText": "ภาษา", + "moddingGuideText": "คู่มือการม็อด", + "mustRestartText": "คุณต้องเริ่มเกมใหม่เพื่อให้สิ่งนี้มีผล", + "netTestingText": "การทดสอบเครือข่าย", + "resetText": "รีเซ็ต", + "showBombTrajectoriesText": "แสดงวิถีลูกระเบิด", + "showPlayerNamesText": "แสดงชื่อผู้เล่น", + "showUserModsText": "แสดงโฟลเดอร์ Mods", + "titleText": "ขั้นสูง", + "translationEditorButtonText": "${APP_NAME} ผู้แก้ไขการแปล", + "translationFetchErrorText": "สถานะการแปลไม่พร้อมใช้งาน", + "translationFetchingStatusText": "กำลังตรวจสอบสถานะการแปล...", + "translationInformMe": "แจ้งฉันเมื่อภาษาของฉันต้องการการอัปเดต", + "translationNoUpdateNeededText": "ภาษาปัจจุบันเป็นปัจจุบัน วู้ฮู!", + "translationUpdateNeededText": "** ภาษาปัจจุบันต้องการการอัปเดต !! **", + "vrTestingText": "การทดสอบ VR" + }, + "shareText": "แบ่งปัน", + "sharingText": "การแบ่งปัน...", + "showText": "แสดง", + "signInForPromoCodeText": "คุณต้องลงชื่อเข้าใช้บัญชีเพื่อให้รหัสมีผล", + "signInWithGameCenterText": "ในการใช้บัญชี Game Center\nลงชื่อเข้าใช้ด้วยแอพ Game Center", + "singleGamePlaylistNameText": "แค่ ${GAME}", + "singlePlayerCountText": "ผู้เล่น 1 คน", + "soloNameFilterText": "เดี่ยว ${NAME}", + "soundtrackTypeNames": { + "CharSelect": "การเลือกตัวละคร", + "Chosen One": "ผู้ถูกเลือก", + "Epic": "เกมโหมดมหากาพย์", + "Epic Race": "มหากาพย์การแข่งขัน", + "FlagCatcher": "ยึดธง", + "Flying": "ความคิดที่เป็นสุข", + "Football": "ฟุตบอล", + "ForwardMarch": "จู่โจม", + "GrandRomp": "พิชิต", + "Hockey": "ฮอกกี้", + "Keep Away": "ระวังอย่าเข้าไปใกล้", + "Marching": "วิ่งไปรอบ ๆ", + "Menu": "เมนูหลัก", + "Onslaught": "การโจมตี", + "Race": "แข่ง", + "Scary": "ราชาแห่งขุนเขา", + "Scores": "หน้าจอคะแนน", + "Survival": "กำจัด", + "ToTheDeath": "นัดสุดท้าย", + "Victory": "หน้าจอคะแนนสุดท้าย" + }, + "spaceKeyText": "เว้นวรรค", + "statsText": "ข้อมูล", + "storagePermissionAccessText": "สิ่งนี้ต้องการการเข้าถึงที่เก็บข้อมูล", + "store": { + "alreadyOwnText": "คุณเป็นเจ้าของ ${NAME} แล้ว!", + "bombSquadProNameText": "${APP_NAME} โปร", + "bombSquadProNewDescriptionText": "• ลบโฆษณาในเกมและหน้าจอจู้จี้\n• ปลดล็อกการตั้งค่าเกมเพิ่มเติม\n• รวมถึง:", + "buyText": "ซื้อ", + "charactersText": "ตัวละคร", + "comingSoonText": "เร็ว ๆ นี้...", + "extrasText": "พิเศษ", + "freeBombSquadProText": "BombSquad เปิดให้เล่นฟรีแล้ว แต่เนื่องจากคุณซื้อมันมาในตอนแรก คุณจึงกลายเป็น\nรับการอัปเกรด BombSquad Pro และตั๋ว ${COUNT} เป็นการขอบคุณ\nเพลิดเพลินกับคุณสมบัติใหม่ และขอขอบคุณสำหรับการสนับสนุนของคุณ!\n-เอริค", + "holidaySpecialText": "วันหยุดพิเศษ", + "howToSwitchCharactersText": "(ไปที่ \"${SETTINGS} -> ${PLAYER_PROFILES}\" เพื่อกำหนดและปรับแต่งตัวละคร)", + "howToUseIconsText": "(สร้างโปรไฟล์ผู้เล่นทั่วโลก (ในหน้าต่างบัญชี) เพื่อใช้สิ่งเหล่านี้)", + "howToUseMapsText": "(ใช้แผนที่เหล่านี้ในทีมของคุณเอง/เพลย์ลิสต์ฟรีสำหรับทุกคน)", + "iconsText": "ไอคอน", + "loadErrorText": "ไม่สามารถโหลดหน้า\nตรวจสอบการเชื่อมต่ออินเทอร์เน็ตของคุณ", + "loadingText": "กำลังโหลด", + "mapsText": "แผนที่", + "miniGamesText": "มินิเกม", + "oneTimeOnlyText": "(ครั้งเดียวเท่านั้น)", + "purchaseAlreadyInProgressText": "การสั่งซื้อรายการนี้อยู่ระหว่างดำเนินการ", + "purchaseConfirmText": "คุณแน่ใจที่จะสั่งซื้อ ${ITEM} หรือไม่?", + "purchaseNotValidError": "สั่งซื้อไม่สำเร็จ\nติดต่อ ${EMAIL} ถ้าสิ่งนี้เป็นข้อผิดพลาด", + "purchaseText": "สั่งซื้อ", + "saleBundleText": "ขายยกชุด!", + "saleExclaimText": "ขาย!", + "salePercentText": "(ลด ${PERCENT}%)", + "saleText": "ลดราคา", + "searchText": "ค้นหา", + "teamsFreeForAllGamesText": "ทีม / เดี่ยว", + "totalWorthText": "*** มูลค่า ${TOTAL_WORTH}! ***", + "upgradeQuestionText": "อัพเกรด?", + "winterSpecialText": "พิเศษฤดูหนาว", + "youOwnThisText": "- คุณเป็นเจ้าของสิ่งนี้ -" + }, + "storeDescriptionText": "8 ผู้เล่นปาร์ตี้เกมบ้า!\n\nระเบิดเพื่อนของคุณ (หรือคอมพิวเตอร์) ในทัวร์นาเมนต์มินิเกมระเบิดเช่น Capture-the-Flag, Bomber-Hockey และ Epic-Slow-Motion-Death-Match!\n\nการควบคุมที่เรียบง่ายและการรองรับคอนโทรลเลอร์ที่ครอบคลุมทำให้ผู้คนมากถึง 8 คนสามารถลงมือได้ คุณยังสามารถใช้อุปกรณ์มือถือของคุณเป็นตัวควบคุมผ่านแอพ 'BombSquad Remote' ฟรี!\n\nระเบิดออกไป!\n\nตรวจสอบ www.froemling.net/bombsquad สำหรับข้อมูลเพิ่มเติม", + "storeDescriptions": { + "blowUpYourFriendsText": "ระเบิดเพื่อนของคุณ", + "competeInMiniGamesText": "แข่งขันในมินิเกมตั้งแต่การแข่งรถไปจนถึงการบิน", + "customize2Text": "ปรับแต่งตัวละคร มินิเกม และแม้แต่เพลงประกอบ", + "customizeText": "ปรับแต่งตัวละครและสร้างรายการเล่นมินิเกมของคุณเอง", + "sportsMoreFunText": "กีฬาสนุกยิ่งขึ้นด้วยระเบิด", + "teamUpAgainstComputerText": "ร่วมทีมกับคอมพิวเตอร์" + }, + "storeText": "ร้านค้า", + "submitText": "ส่ง", + "submittingPromoCodeText": "กำลังส่งรหัส...", + "teamNamesColorText": "Team Names/Colors...", + "telnetAccessGrantedText": "เปิดใช้งานการเข้าถึง Telnet", + "telnetAccessText": "ตรวจพบการเข้าถึง Telnet; อนุญาต?", + "testBuildErrorText": "รุ่นทดสอบนี้ไม่ทำงานอีกต่อไป โปรดตรวจสอบเวอร์ชันใหม่", + "testBuildText": "ทดสอบบิลด์", + "testBuildValidateErrorText": "ไม่สามารถตรวจสอบการสร้างการทดสอบ (ไม่มีการเชื่อมต่อเน็ต?)", + "testBuildValidatedText": "ทดสอบบิลด์ที่ตรวจสอบแล้ว; สนุก!", + "thankYouText": "ขอบคุณสำหรับการสนับสนุนของคุณ! สนุกกับเกม!!", + "threeKillText": "ฆ่าสามครั้ง!!", + "timeBonusText": "โบนัสเวลา", + "timeElapsedText": "เวลาที่ผ่านไป", + "timeExpiredText": "เวลาหมด", + "timeSuffixDaysText": "${COUNT}ว", + "timeSuffixHoursText": "${COUNT}ชม", + "timeSuffixMinutesText": "${COUNT}น", + "timeSuffixSecondsText": "${COUNT}วิ", + "tipText": "เคล็ดลับ", + "titleText": "BombSquad", + "titleVRText": "BombSquad VR", + "topFriendsText": "เพื่อนที่ดีที่สุด", + "tournamentCheckingStateText": "การตรวจสอบสถานะการแข่งขัน โปรดรอ...", + "tournamentEndedText": "การแข่งขันนี้สิ้นสุดลงแล้ว ใหม่จะเริ่มเร็ว ๆ นี้", + "tournamentEntryText": "รายการแข่งขัน", + "tournamentResultsRecentText": "ผลการแข่งขันล่าสุด", + "tournamentStandingsText": "อันดับการแข่งขัน", + "tournamentText": "การแข่งขัน", + "tournamentTimeExpiredText": "เวลาการแข่งขันหมดอายุ", + "tournamentsText": "การแข่งขัน", + "translations": { + "characterNames": { + "Agent Johnson": "Agent Johnson", + "B-9000": "B-9000", + "Bernard": "Bernard", + "Bones": "Bones", + "Butch": "Butch", + "Easter Bunny": "Easter Bunny", + "Flopsy": "Flopsy", + "Frosty": "Frosty", + "Gretel": "Gretel", + "Grumbledorf": "Grumbledorf", + "Jack Morgan": "Jack Morgan", + "Kronk": "Kronk", + "Lee": "Lee", + "Lucky": "Lucky", + "Mel": "Mel", + "Middle-Man": "Middle-Man", + "Minimus": "Minimus", + "Pascal": "Pascal", + "Pixel": "Pixel", + "Sammy Slam": "Sammy Slam", + "Santa Claus": "Santa Claus", + "Snake Shadow": "Snake Shadow", + "Spaz": "Spaz", + "Taobao Mascot": "Taobao Mascot", + "Todd McBurton": "Todd McBurton", + "Zoe": "Zoe", + "Zola": "Zola" + }, + "coopLevelNames": { + "${GAME} Training": "${GAME} การฝึกซ้อม", + "Infinite ${GAME}": "ไม่มีที่สิ้นสุด ${GAME}", + "Infinite Onslaught": "การโจมตีที่ไม่มีที่สิ้นสุด", + "Infinite Runaround": "วิ่งรอบ ๆ ไม่มีที่สิ้นสุด", + "Onslaught Training": "การฝึกจู่โจม", + "Pro ${GAME}": "โปร ${GAME}", + "Pro Football": "โปรฟุตบอล", + "Pro Onslaught": "โปรจู่โจม", + "Pro Runaround": "โปรวิ่งรอบ ๆ", + "Rookie ${GAME}": "มือใหม่ ${GAME}", + "Rookie Football": "ฟุตบอลมือใหม่", + "Rookie Onslaught": "มือใหม่จู่โจม", + "The Last Stand": "ยืนสุดท้าย", + "Uber ${GAME}": "Uber ${GAME}", + "Uber Football": "Uber Football", + "Uber Onslaught": "Uber Onslaught", + "Uber Runaround": "Uber Runaround" + }, + "gameDescriptions": { + "Be the chosen one for a length of time to win.\nKill the chosen one to become it.": "เป็นผู้ที่ได้รับเลือกเป็นระยะเวลาหนึ่งเพื่อชนะ\nฆ่าคนที่ถูกเลือกให้เป็น", + "Bomb as many targets as you can.": "วางระเบิดเป้าหมายให้ได้มากที่สุด", + "Carry the flag for ${ARG1} seconds.": "ถือธงเป็นเวลา ${ARG1} วินาที", + "Carry the flag for a set length of time.": "ถือธงตามระยะเวลาที่กำหนด", + "Crush ${ARG1} of your enemies.": "บดขยี้ศัตรูของคุณ ${ARG1} คน", + "Defeat all enemies.": "เอาชนะศัตรูทั้งหมด", + "Dodge the falling bombs.": "หลบระเบิดที่ตกลงมา", + "Final glorious epic slow motion battle to the death.": "มหากาพย์การต่อสู้แบบสโลว์โมชั่นสุดท้ายอันรุ่งโรจน์สู่ความตาย", + "Gather eggs!": "เก็บไข่!", + "Get the flag to the enemy end zone.": "ถือธงไปยังฐานของศัตรู", + "How fast can you defeat the ninjas?": "คุณสามารถเอาชนะนินจาได้เร็วแค่ไหน?", + "Kill a set number of enemies to win.": "ฆ่าศัตรูตามจำนวนที่กำหนดเพื่อชนะ", + "Last one standing wins.": "รอดคนสุดท้ายชนะ", + "Last remaining alive wins.": "ชัยชนะที่เหลืออยู่", + "Last team standing wins.": "รอดทีมสุดท้ายชนะ", + "Prevent enemies from reaching the exit.": "ป้องกันไม่ให้ศัตรูไปถึงทางออก", + "Reach the enemy flag to score.": "ถือธงศัตรูเพื่อทำคะแนน", + "Return the enemy flag to score.": "ส่งธงศัตรูคืนเพื่อทำคะแนน", + "Run ${ARG1} laps.": "วิ่ง ${ARG1} รอบ", + "Run ${ARG1} laps. Your entire team has to finish.": "วิ่ง ${ARG1} รอบ ทีมของคุณทั้งหมดต้องเข้าเส้นชัย", + "Run 1 lap.": "วิ่ง 1 รอบ", + "Run 1 lap. Your entire team has to finish.": "วิ่ง 1 รอบ ทีมของคุณทั้งหมดต้องเข้าเส้นชัย", + "Run real fast!": "วิ่งเร็วจริง!", + "Score ${ARG1} goals.": "ทำประตูได้ ${ARG1} ประตู", + "Score ${ARG1} touchdowns.": "ทำคะแนน ${ARG1} ทัชดาวน์", + "Score a goal.": "ทำประตู", + "Score a touchdown.": "ทำแต้มทัชดาวน์", + "Score some goals.": "ทำประตูได้บ้าง", + "Secure all ${ARG1} flags.": "ยึด ${ARG1} ธง", + "Secure all flags on the map to win.": "ยึดธงทั้งหมดบนแผนที่เพื่อชนะ", + "Secure the flag for ${ARG1} seconds.": "ยึดธงไว้เป็นเวลา ${ARG1} วินาที", + "Secure the flag for a set length of time.": "ยึดธงไว้ตามระยะเวลาที่กำหนด", + "Steal the enemy flag ${ARG1} times.": "ขโมยธงของศัตรู ${ARG1} ครั้ง", + "Steal the enemy flag.": "ขโมยธงศัตรู", + "There can be only one.": "สามารถมีได้เพียงคนเดียว", + "Touch the enemy flag ${ARG1} times.": "แตะธงศัตรู ${ARG1} ครั้ง", + "Touch the enemy flag.": "แตะธงศัตรู", + "carry the flag for ${ARG1} seconds": "ถือธงเป็นเวลา ${ARG1} วินาที", + "kill ${ARG1} enemies": "ฆ่าศัตรู ${ARG1} คน", + "last one standing wins": "รอดคนสุดท้ายชนะ", + "last team standing wins": "รอดทีมสุดท้ายชนะ", + "return ${ARG1} flags": "ส่งคืน ${ARG1} ธง", + "return 1 flag": "ส่งคืน 1 ธง", + "run ${ARG1} laps": "วิ่ง ${ARG1} รอบ", + "run 1 lap": "วิ่ง 1 รอบ", + "score ${ARG1} goals": "ทำประตูได้ ${ARG1} ประตู", + "score ${ARG1} touchdowns": "ทำคะแนน ${ARG1} ทัชดาวน์", + "score a goal": "ทำประตู", + "score a touchdown": "ทำแต้มทัชดาวน์", + "secure all ${ARG1} flags": "ยึด ${ARG1} ธง", + "secure the flag for ${ARG1} seconds": "ยึดธงไว้เป็นเวลา ${ARG1} วินาที", + "touch ${ARG1} flags": "แตะ ${ARG1} ธง", + "touch 1 flag": "แตะ ${ARG1} ธง" + }, + "gameNames": { + "Assault": "จู่โจม", + "Capture the Flag": "ยึดธง", + "Chosen One": "ผู้ถูกเลือก", + "Conquest": "พิชิต", + "Death Match": "นัดสุดท้าย", + "Easter Egg Hunt": "การล่าไข่อิสเตอร์", + "Elimination": "การกำจัด", + "Football": "ฟุตบอล", + "Hockey": "ฮอกกี้", + "Keep Away": "ระวังอย่าเข้าไปใกล้", + "King of the Hill": "ราชาแห่งขุนเขา", + "Meteor Shower": "ฝนดาวตก", + "Ninja Fight": "นินจาต่อสู้", + "Onslaught": "การโจมตี", + "Race": "แข่งขัน", + "Runaround": "วิ่งไปรอบ ๆ", + "Target Practice": "ฝึกเล็งเป้าหมาย", + "The Last Stand": "รอดคนสุดท้าย" + }, + "inputDeviceNames": { + "Keyboard": "แป้นพิมพ์", + "Keyboard P2": "แป้นพิมพ์ คนที่ 2" + }, + "languages": { + "Arabic": "ภาษาอารบิก", + "Belarussian": "ภาษาเบลารุส", + "Chinese": "ภาษาจีนตัวย่อ", + "ChineseTraditional": "ภาษาจีนดั้งเดิม", + "Croatian": "ภาษาโครเอเชีย", + "Czech": "ภาษาเช็ก", + "Danish": "ภาษาเดนมาร์ก", + "Dutch": "ภาษาดัตช์", + "English": "ภาษาอังกฤษ", + "Esperanto": "ภาษาเอสเปรันโต", + "Finnish": "ภาษาฟินแลนด์", + "French": "ภาษาฝรั่งเศส", + "German": "ภาษาเยอรมัน", + "Gibberish": "Gibberish", + "Greek": "กรีก", + "Hindi": "ภาษาฮินดู", + "Hungarian": "ภาษาฮังการี", + "Indonesian": "ภาษาอินโดนีเซีย", + "Italian": "ภาษาอิตาลี", + "Japanese": "ภาษาญี่ปุ่น", + "Korean": "ภาษาเกาหลี", + "Persian": "ภาษาเปอร์เซีย", + "Polish": "ภาษาโปแลนด์", + "Portuguese": "ภาษาโปรตุเกส", + "Romanian": "ภาษาโรมาเนีย", + "Russian": "ภาษารัสเซีย", + "Serbian": "ภาษาเซอร์เบีย", + "Slovak": "ภาษาสโลวัก", + "Spanish": "ภาษาสเปน", + "Swedish": "ภาษาสวีเดน", + "Thai": "ภาษาไทย", + "Turkish": "ภาษาตุรกี", + "Ukrainian": "ยูเครน", + "Venetian": "ภาษาเวนิส", + "Vietnamese": "ภาษาเวียดนาม" + }, + "leagueNames": { + "Bronze": "บรอนซ์", + "Diamond": "ไดมอนด์", + "Gold": "โกลด์", + "Silver": "ซิลเวอร์" + }, + "mapsNames": { + "Big G": "บิ๊ก จี", + "Bridgit": "บริดจิต", + "Courtyard": "ลาน", + "Crag Castle": "ปราสาท แคร็ก", + "Doom Shroom": "ดูมชรูม", + "Football Stadium": "สนามฟุตบอล", + "Happy Thoughts": "ความคิดที่เป็นสุข", + "Hockey Stadium": "สนามฮอกกี้", + "Lake Frigid": "ทะเลสาบฟริจิด", + "Monkey Face": "หน้าลิง", + "Rampage": "อาละวาด", + "Roundabout": "วงเวียน", + "Step Right Up": "ก้าวขวาขึ้น", + "The Pad": "แพด", + "Tip Top": "ทิปท็อป", + "Tower D": "ทาวเวอร์ ดี", + "Zigzag": "ซิกแซก" + }, + "playlistNames": { + "Just Epic": "แค่มหากาพย์", + "Just Sports": "แค่กีฬา" + }, + "scoreNames": { + "Flags": "ธง", + "Goals": "ประตู", + "Score": "คะแนน", + "Survived": "รอด", + "Time": "เวลา", + "Time Held": "เวลาที่ถือไว้" + }, + "serverResponses": { + "A code has already been used on this account.": "มีการใช้รหัสในบัญชีนี้แล้ว", + "A reward has already been given for that address.": "ได้รับรางวัลสำหรับที่อยู่นั้นแล้ว", + "Account linking successful!": "เชื่อมโยงบัญชีสำเร็จ!", + "Account unlinking successful!": "ยกเลิกการเชื่อมโยงบัญชีสำเร็จ!", + "Accounts are already linked.": "บัญชีถูกเชื่อมโยงแล้ว", + "Ad view could not be verified.\nPlease be sure you are running an official and up-to-date version of the game.": "ไม่สามารถตรวจสอบการดูโฆษณาได้\nโปรดตรวจสอบให้แน่ใจว่าคุณกำลังใช้งานเกมเวอร์ชันที่เป็นทางการและเป็นปัจจุบัน", + "An error has occurred; (${ERROR})": "เกิดข้อผิดพลาด; (${ERROR})", + "An error has occurred; please contact support. (${ERROR})": "เกิดข้อผิดพลาด; โปรดติดต่อฝ่ายสนับสนุน (${ERROR})", + "An error has occurred; please contact support@froemling.net.": "เกิดข้อผิดพลาด; โปรดติดต่อ support@froemling.net", + "An error has occurred; please try again later.": "เกิดข้อผิดพลาด; โปรดลองอีกครั้งในภายหลัง.", + "Are you sure you want to link these accounts?\n\n${ACCOUNT1}\n${ACCOUNT2}\n\nThis cannot be undone!": "คุณแน่ใจหรือไม่ว่าต้องการเชื่อมโยงบัญชีเหล่านี้\n\n${ACCOUNT1}\n${ACCOUNT2} \n\nสิ่งนี้ไม่สามารถยกเลิกได้!", + "BombSquad Pro unlocked!": "BombSquad Pro ปลดล็อคแล้ว!", + "Can't link 2 accounts of this type.": "ไม่สามารถเชื่อมโยง 2 บัญชีประเภทนี้", + "Can't link 2 diamond league accounts.": "ไม่สามารถเชื่อมโยงบัญชีลีกไดมอนด์ 2 บัญชีได้", + "Can't link; would surpass maximum of ${COUNT} linked accounts.": "ไม่สามารถเชื่อมโยง; จะเกินบัญชีที่เชื่อมโยงสูงสุด ${COUNT} บัญชี", + "Cheating detected; scores and prizes suspended for ${COUNT} days.": "ตรวจพบการโกง; คะแนนและรางวัลถูกระงับเป็นเวลา ${COUNT} วัน", + "Could not establish a secure connection.": "ไม่สามารถสร้างการเชื่อมต่อที่ปลอดภัย", + "Daily maximum reached.": "ถึงสูงสุดรายวันแล้ว", + "Entering tournament...": "เข้าสู่การแข่งขัน...", + "Invalid code.": "รหัสไม่ถูกต้อง", + "Invalid payment; purchase canceled.": "การชำระเงินไม่ถูกต้อง การซื้อถูกยกเลิก", + "Invalid promo code.": "รหัสโปรโหมทไม่ถูกต้อง", + "Invalid purchase.": "การซื้อที่ไม่ถูกต้อง", + "Invalid tournament entry; score will be ignored.": "รายการทัวร์นาเมนต์ไม่ถูกต้อง คะแนนจะถูกละเว้น", + "Item unlocked!": "ปลดล็อคไอเทมแล้ว!", + "LINKING DENIED. ${ACCOUNT} contains\nsignificant data that would ALL BE LOST.\nYou can link in the opposite order if you'd like\n(and lose THIS account's data instead)": "การเชื่อมโยงถูกปฏิเสธ ${ACCOUNT} ประกอบด้วย\nข้อมูลสำคัญที่จะสูญหายทั้งหมด\nคุณสามารถเชื่อมโยงในลำดับตรงกันข้ามหากคุณต้องการ\n(และสูญเสียข้อมูลของบัญชีนี้แทน)", + "Link account ${ACCOUNT} to this account?\nAll existing data on ${ACCOUNT} will be lost.\nThis can not be undone. Are you sure?": "เชื่อมโยงบัญชี ${ACCOUNT} กับบัญชีนี้หรือไม่\nข้อมูลที่มีอยู่ทั้งหมดใน ${ACCOUNT} จะหายไป\nไม่สามารถยกเลิกได้ คุณแน่ใจไหม?", + "Max number of playlists reached.": "ถึงจำนวนเพลย์ลิสต์สูงสุดแล้ว", + "Max number of profiles reached.": "ถึงจำนวนโปรไฟล์สูงสุดแล้ว", + "Maximum friend code rewards reached.": "รางวัลรหัสเพื่อนถึงขีดจำกัดแล้ว", + "Message is too long.": "ข้อความยาวเกินไป", + "No servers are available. Please try again soon.": "ไม่มีเซิร์ฟเวอร์ที่พร้อมใช้งาน โปรดลองอีกครั้งในเร็วๆ นี้", + "Profile \"${NAME}\" upgraded successfully.": "อัปเกรดโปรไฟล์ \"${NAME}\" สำเร็จแล้ว", + "Profile could not be upgraded.": "ไม่สามารถอัพเกรดโปรไฟล์ได้", + "Purchase successful!": "ซื้อสำเร็จ!", + "Received ${COUNT} tickets for signing in.\nCome back tomorrow to receive ${TOMORROW_COUNT}.": "ได้รับตั๋ว ${COUNT} ใบสำหรับการลงชื่อเข้าใช้\nกลับมาพรุ่งนี้เพื่อรับ ${TOMORROW_COUNT}", + "Server functionality is no longer supported in this version of the game;\nPlease update to a newer version.": "ไม่รองรับการทำงานของเซิร์ฟเวอร์ในเกมเวอร์ชั่นนี้อีกต่อไป\nโปรดอัปเดตเป็นเวอร์ชันที่ใหม่กว่า", + "Sorry, there are no uses remaining on this code.": "ขออภัย ไม่มีการใช้งานที่เหลืออยู่ในรหัสนี้", + "Sorry, this code has already been used.": "ขออภัย รหัสนี้ถูกใช้ไปแล้ว", + "Sorry, this code has expired.": "ขออภัย รหัสนี้หมดอายุแล้ว", + "Sorry, this code only works for new accounts.": "ขออภัย รหัสนี้ใช้ได้กับบัญชีใหม่เท่านั้น", + "Still searching for nearby servers; please try again soon.": "ยังคงค้นหาเซิร์ฟเวอร์ใกล้เคียง โปรดลองอีกครั้งในเร็วๆ นี้", + "Temporarily unavailable; please try again later.": "ปิดให้บริการชั่วคราว; โปรดลองอีกครั้งในภายหลัง.", + "The tournament ended before you finished.": "การแข่งขันสิ้นสุดลงก่อนที่คุณจะเสร็จสิ้น", + "This account cannot be unlinked for ${NUM} days.": "บัญชีนี้ไม่สามารถยกเลิกการเชื่อมโยงได้เป็นเวลา ${NUM} วัน", + "This code cannot be used on the account that created it.": "ไม่สามารถใช้รหัสนี้กับบัญชีที่สร้างรหัสได้", + "This is currently unavailable; please try again later.": "ไม่สามารถใช้งานได้ในขณะนี้ โปรดลองอีกครั้งในภายหลัง.", + "This requires version ${VERSION} or newer.": "ต้องใช้เวอร์ชัน ${VERSION} หรือใหม่กว่า", + "Tournaments disabled due to rooted device.": "การแข่งขันถูกปิดใช้งานเนื่องจากอุปกรณ์ที่รูท", + "Tournaments require ${VERSION} or newer": "ทัวร์นาเมนต์ต้องใช้ ${VERSION} หรือใหม่กว่า", + "Unlink ${ACCOUNT} from this account?\nAll data on ${ACCOUNT} will be reset.\n(except for achievements in some cases)": "ยกเลิกการเชื่อมโยง ${ACCOUNT} จากบัญชีนี้หรือไม่\nข้อมูลทั้งหมดใน ${ACCOUNT} จะถูกรีเซ็ต\n(ยกเว้นความสำเร็จในบางกรณี)", + "WARNING: complaints of hacking have been issued against your account.\nAccounts found to be hacking will be banned. Please play fair.": "คำเตือน: มีการร้องเรียนเกี่ยวกับการแฮ็กบัญชีของคุณ\nบัญชีที่พบว่ามีการแฮ็คจะถูกแบน กรุณาเล่นอย่างยุติธรรม", + "Would you like to link your device account to this one?\n\nYour device account is ${ACCOUNT1}\nThis account is ${ACCOUNT2}\n\nThis will allow you to keep your existing progress.\nWarning: this cannot be undone!\n": "คุณต้องการเชื่อมโยงบัญชีอุปกรณ์ของคุณกับบัญชีนี้หรือไม่?\n\nบัญชีอุปกรณ์ของคุณคือ ${ACCOUNT1}\nบัญชีนี้คือ ${ACCOUNT2}\n\nนี้จะช่วยให้คุณรักษาความคืบหน้าที่มีอยู่ของคุณ\nคำเตือน: สิ่งนี้ไม่สามารถยกเลิกได้!", + "You already own this!": "คุณเป็นเจ้าของสิ่งนี้แล้ว!", + "You can join in ${COUNT} seconds.": "คุณสามารถเข้าร่วมได้ภายในอีก ${COUNT} วินาที", + "You don't have enough tickets for this!": "คุณมีตั๋วไม่เพียงพอสำหรับสิ่งนี้!", + "You don't own that.": "คุณไม่ได้เป็นเจ้าของสิ่งนั้น", + "You got ${COUNT} tickets!": "คุณได้รับตั๋ว ${COUNT} ใบ!", + "You got a ${ITEM}!": "คุณได้รับ ${ITEM}!", + "You have been promoted to a new league; congratulations!": "คุณได้รับการเลื่อนตำแหน่งเป็นลีกใหม่ ยินดีด้วย!", + "You must update to a newer version of the app to do this.": "คุณต้องอัปเดตแอปเป็นเวอร์ชันใหม่กว่าจึงจะทำได้", + "You must update to the newest version of the game to do this.": "คุณต้องอัปเดตเป็นเวอร์ชันใหม่ล่าสุดของเกมจึงจะสามารถทำได้", + "You must wait a few seconds before entering a new code.": "คุณต้องรอสักครู่ก่อนที่จะป้อนรหัสใหม่", + "You ranked #${RANK} in the last tournament. Thanks for playing!": "คุณติดอันดับ #${RANK} ในทัวร์นาเมนต์ที่แล้ว ขอบคุณสำหรับการเล่น!", + "Your account was rejected. Are you signed in?": "บัญชีของคุณถูกปฏิเสธ คุณลงชื่อเข้าใช้หรือไม่", + "Your copy of the game has been modified.\nPlease revert any changes and try again.": "สำเนาเกมของคุณได้รับการแก้ไขแล้ว\nโปรดยกเลิกการเปลี่ยนแปลงและลองอีกครั้ง", + "Your friend code was used by ${ACCOUNT}": "รหัสเพื่อนของคุณถูกใช้โดย ${ACCOUNT}" + }, + "settingNames": { + "1 Minute": "1 นาที", + "1 Second": "1 วินาที", + "10 Minutes": "10 นาที", + "2 Minutes": "2 นาที", + "2 Seconds": "2 วินาที", + "20 Minutes": "20 นาที", + "4 Seconds": "4 วินาที", + "5 Minutes": "5 นาที", + "8 Seconds": "8 วินาที", + "Allow Negative Scores": "อนุญาตให้คะแนนติดลบ", + "Balance Total Lives": "ยอดรวมชีวิต", + "Bomb Spawning": "ระเบิดเกิด", + "Chosen One Gets Gloves": "ผู้ถูกเลือกรับถุงมือ", + "Chosen One Gets Shield": "ผู้ถูกเลือกรับโล่", + "Chosen One Time": "เวลาผู้ถูกเลือก", + "Enable Impact Bombs": "เปิดใช้งาน Impact Bombs", + "Enable Triple Bombs": "เปิดใช้งาน Triple Bombs", + "Entire Team Must Finish": "ทั้งทีมต้องเข้าเส้นชัย", + "Epic Mode": "โหมดมหากาพย์", + "Flag Idle Return Time": "Flag Idle Return Time", + "Flag Touch Return Time": "Flag Touch Return Time", + "Hold Time": "Hold Time", + "Kills to Win Per Player": "ฆ่าเพื่อชนะต่อผู้เล่น", + "Laps": "รอบ", + "Lives Per Player": "ชีวิตต่อผู้เล่น", + "Long": "ยาว", + "Longer": "ยาวกว่า", + "Mine Spawning": "Mine Spawning", + "No Mines": "No Mines", + "None": "ไม่มี", + "Normal": "ปกติ", + "Pro Mode": "โหมดโปร", + "Respawn Times": "เวลาเกิดใหม่", + "Score to Win": "คะแนนที่จะชนะ", + "Short": "สั้น", + "Shorter": "สั้นกว่า", + "Solo Mode": "โหมดโซโล", + "Target Count": "จำนวนเป้าหมาย", + "Time Limit": "เวลาที่ จำกัด" + }, + "statements": { + "${TEAM} is disqualified because ${PLAYER} left": "${TEAM} ถูกตัดสิทธิ์เนื่องจาก ${PLAYER} ออก", + "Killing ${NAME} for skipping part of the track!": "สังหาร ${NAME} จากการกระโดดข้ามแทร็ก!", + "Warning to ${NAME}: turbo / button-spamming knocks you out.": "คำเตือนถึง ${NAME}: สแปม / สแปมของปุ่มทำให้คุณล้มลง" + }, + "teamNames": { + "Bad Guys": "คนเลว", + "Blue": "สีฟ้า", + "Good Guys": "คนดี", + "Red": "สีแดง" + }, + "tips": { + "A perfectly timed running-jumping-spin-punch can kill in a single hit\nand earn you lifelong respect from your friends.": "วิ่ง-กระโดด-ปั่น-ต่อยอย่างหมดเวลาสามารถฆ่าได้ในการโจมตีครั้งเดียว\nและได้รับความเคารพตลอดชีวิตจากเพื่อนของคุณ", + "Always remember to floss.": "อย่าลืมใช้ไหมขัดฟัน", + "Create player profiles for yourself and your friends with\nyour preferred names and appearances instead of using random ones.": "สร้างโปรไฟล์ผู้เล่นสำหรับตัวคุณเองและเพื่อนของคุณด้วย\nชื่อและรูปลักษณ์ที่คุณต้องการแทนที่จะใช้ชื่อแบบสุ่ม", + "Curse boxes turn you into a ticking time bomb.\nThe only cure is to quickly grab a health-pack.": "กล่องคำสาปจะทำให้คุณกลายเป็นระเบิดเวลา\nทางเดียวที่รักษาได้คือรีบคว้าชุดสุขภาพ", + "Despite their looks, all characters' abilities are identical,\nso just pick whichever one you most closely resemble.": "แม้จะมีรูปลักษณ์ แต่ความสามารถของตัวละครทั้งหมดก็เหมือนกัน\nดังนั้นเพียงแค่เลือกอันที่คุณใกล้เคียงที่สุด", + "Don't get too cocky with that energy shield; you can still get yourself thrown off a cliff.": "อย่าอวดดีเกินไปกับโล่พลังงานนั้น คุณยังสามารถทำให้ตัวเองตกจากหน้าผาได้", + "Don't run all the time. Really. You will fall off cliffs.": "อย่าวิ่งตลอดเวลา จริงหรือ. คุณจะตกหน้าผา", + "Don't spin for too long; you'll become dizzy and fall.": "อย่าหมุนนานเกินไป คุณจะเวียนหัวและล้มลง", + "Hold any button to run. (Trigger buttons work well if you have them)": "กดค้างปุ่มใดก็ได้เพื่อวิ่ง (ปุ่มทริกเกอร์ทำงานได้ดีถ้าคุณมี)", + "Hold down any button to run. You'll get places faster\nbut won't turn very well, so watch out for cliffs.": "กดค้างปุ่มใดก็ได้เพื่อวิ่ง คุณจะได้สถานที่เร็วขึ้น\nแต่เลี้ยวไม่ค่อยดี ระวังหน้าผานะ", + "Ice bombs are not very powerful, but they freeze\nwhoever they hit, leaving them vulnerable to shattering.": "ระเบิดน้ำแข็งไม่แรงมาก แต่มันแข็ง\nใครก็ตามที่พวกเขาตี ปล่อยให้พวกเขาเสี่ยงที่จะแตกเป็นเสี่ยงๆ", + "If someone picks you up, punch them and they'll let go.\nThis works in real life too.": "ถ้ามีคนมารับคุณ ต่อยเขาแล้วเขาจะปล่อย\nสิ่งนี้ใช้ได้ผลในชีวิตจริงด้วย", + "If you are short on controllers, install the '${REMOTE_APP_NAME}' app\non your mobile devices to use them as controllers.": "หากคุณขาดคอนโทรลเลอร์ ให้ติดตั้งแอป \"${REMOTE_APP_NAME}\"\nบนอุปกรณ์มือถือของคุณเพื่อใช้เป็นคอนโทรลเลอร์", + "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.": "หากคุณมีระเบิดเหนียวติดอยู่ ให้กระโดดไปรอบๆ และหมุนเป็นวงกลม คุณอาจ\nสลัดทิ้งระเบิด หรือถ้าไม่มีอะไรอย่างอื่นช่วงเวลาสุดท้ายของคุณจะสนุกสนาน", + "If you kill an enemy in one hit you get double points for it.": "หากคุณฆ่าศัตรูในการโจมตีครั้งเดียว คุณจะได้รับคะแนนสองเท่าสำหรับมัน", + "If you pick up a curse, your only hope for survival is to\nfind a health powerup in the next few seconds.": "หากคุณรับคำสาป ความหวังเดียวในการเอาตัวรอดคือ\nค้นหาแพ็คยาในอีกไม่กี่วินาทีข้างหน้า", + "If you stay in one place, you're toast. Run and dodge to survive..": "หากคุณอยู่ในที่เดียว วิ่งหนีเอาตัวรอด..", + "If you've got lots of players coming and going, turn on 'auto-kick-idle-players'\nunder settings in case anyone forgets to leave the game.": "หากคุณมีผู้เล่นจำนวนมากเข้าและออก ให้เปิด 'เตะผู้เล่นอัตโนมัติ'\nภายใต้การตั้งค่าในกรณีที่ใครลืมออกจากเกม", + "If your device gets too warm or you'd like to conserve battery power,\nturn down \"Visuals\" or \"Resolution\" in Settings->Graphics": "หากอุปกรณ์ของคุณร้อนเกินไปหรือคุณต้องการประหยัดพลังงานแบตเตอรี่\nลด \"ภาพ\" หรือ \"ความละเอียด\" ในการตั้งค่า -> กราฟิก", + "If your framerate is choppy, try turning down resolution\nor visuals in the game's graphics settings.": "หากอัตราเฟรมของคุณขาดๆ หายๆ ให้ลองลดความละเอียดลง\nหรือภาพในการตั้งค่ากราฟิกของเกม", + "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.": "ในการยึดธง ธงของคุณเองจะต้องอยู่ที่ฐานของคุณเพื่อทำคะแนน ถ้าอีกอันหนึ่ง\nทีมกำลังจะทำคะแนน การขโมยธงอาจเป็นวิธีที่ดีในการหยุดพวกเขา", + "In hockey, you'll maintain more speed if you turn gradually.": "ในฮ็อกกี้ คุณจะรักษาความเร็วได้มากขึ้นหากคุณค่อยๆ เลี้ยว", + "It's easier to win with a friend or two helping.": "มันง่ายกว่าที่จะชนะกับเพื่อนหรือสองคนช่วย", + "Jump just as you're throwing to get bombs up to the highest levels.": "กระโดดในขณะที่คุณกำลังขว้างระเบิดขึ้นไปสูงขึ้น", + "Land-mines are a good way to stop speedy enemies.": "ทุ่นระเบิดเป็นวิธีที่ดีในการหยุดศัตรูที่รวดเร็ว", + "Many things can be picked up and thrown, including other players. Tossing\nyour enemies off cliffs can be an effective and emotionally fulfilling strategy.": "หยิบของได้หลายอย่าง รวมทั้งผู้เล่นคนอื่นด้วย โยน\nศัตรูของคุณนอกหน้าผาอาจเป็นกลยุทธ์ที่มีประสิทธิภาพและตอบสนองทางอารมณ์", + "No, you can't get up on the ledge. You have to throw bombs.": "ไม่ คุณไม่สามารถขึ้นไปบนหิ้งได้ คุณต้องขว้างระเบิด", + "Players can join and leave in the middle of most games,\nand you can also plug and unplug controllers on the fly.": "ผู้เล่นสามารถเข้าร่วมและออกจากเกมส่วนใหญ่ได้\nและคุณยังสามารถเสียบและถอดปลั๊กคอนโทรลเลอร์ได้ทันที", + "Practice using your momentum to throw bombs more accurately.": "ฝึกใช้โมเมนตัมในการขว้างระเบิดให้แม่นยำยิ่งขึ้น", + "Punches do more damage the faster your fists are moving,\nso try running, jumping, and spinning like crazy.": "การชกสร้างความเสียหายมากขึ้นเมื่อหมัดของคุณเคลื่อนไหวเร็วขึ้น\nดังนั้นลองวิ่ง กระโดด และหมุนอย่างบ้าคลั่ง", + "Run back and forth before throwing a bomb\nto 'whiplash' it and throw it farther.": "วิ่งกลับไปกลับมาก่อนจะปาระเบิด\nเพื่อ 'แส้' และโยนมันออกไปไกลขึ้น", + "Take out a group of enemies by\nsetting off a bomb near a TNT box.": "นำกลุ่มศัตรูออกไปโดย\nวางระเบิดใกล้กล่องทีเอ็นที", + "The head is the most vulnerable area, so a sticky-bomb\nto the noggin usually means game-over.": "ส่วนหัวเป็นบริเวณที่เปราะบางที่สุด ดังนั้น ระเบิดเหนียว\nto the noggin มักจะหมายถึงการจบเกม", + "This level never ends, but a high score here\nwill earn you eternal respect throughout the world.": "ระดับนี้ไม่สิ้นสุด แต่คะแนนสูงที่นี่\nจะได้รับความเคารพชั่วนิรันดร์จากทั่วโลก", + "Throw strength is based on the direction you are holding.\nTo toss something gently in front of you, don't hold any direction.": "กำลังขว้างจะขึ้นอยู่กับทิศทางที่คุณถืออยู่\nโยนบางสิ่งต่อหน้าคุณอย่างแผ่วเบาอย่าถือทิศทางใด ๆ", + "Tired of the soundtrack? Replace it with your own!\nSee Settings->Audio->Soundtrack": "เบื่อเพลงประกอบละคร? แทนที่ด้วยของคุณเอง!\nดูการตั้งค่า -> เสียง -> เพลงประกอบ", + "Try 'Cooking off' bombs for a second or two before throwing them.": "ลอง 'ทำอาหาร' ระเบิดสักสองสามวินาทีก่อนจะโยนทิ้ง", + "Try tricking enemies into killing eachother or running off cliffs.": "ลองหลอกให้ศัตรูฆ่ากันเองหรือวิ่งหนีจากหน้าผา", + "Use the pick-up button to grab the flag < ${PICKUP} >": "ใช้ปุ่มรับธงเพื่อคว้าธง < ${PICKUP} >", + "Whip back and forth to get more distance on your throws..": "ตีกลับไปกลับมาเพื่อให้โยนได้ไกลขึ้น..", + "You can 'aim' your punches by spinning left or right.\nThis is useful for knocking bad guys off edges or scoring in hockey.": "คุณสามารถ 'เล็ง' หมัดของคุณโดยหมุนไปทางซ้ายหรือขวา\nสิ่งนี้มีประโยชน์สำหรับการกำจัดคนเลวออกจากขอบหรือให้คะแนนในฮ็อกกี้", + "You can judge when a bomb is going to explode based on the\ncolor of sparks from its fuse: yellow..orange..red..BOOM.": "คุณสามารถตัดสินได้ว่าเมื่อใดที่ระเบิดจะระเบิดขึ้นอยู่กับ\nสีของประกายไฟจากฟิวส์: เหลือง..ส้ม..แดง..บูม.", + "You can throw bombs higher if you jump just before throwing.": "คุณสามารถขว้างระเบิดให้สูงขึ้นได้หากคุณกระโดดก่อนขว้าง", + "You take damage when you whack your head on things,\nso try to not whack your head on things.": "คุณได้รับความเสียหายเมื่อคุณตีหัวกับสิ่งของ\nดังนั้นอย่าพยายามตีหัวของคุณกับสิ่งต่างๆ", + "Your punches do much more damage if you are running or spinning.": "หมัดของคุณสร้างความเสียหายได้มากกว่าถ้าคุณวิ่งหรือหมุน" + } + }, + "trophiesRequiredText": "ต้องมีอย่างน้อย ${NUMBER} ถ้วยรางวัล", + "trophiesText": "ถ้วยรางวัล", + "trophiesThisSeasonText": "ถ้วยรางวัลในฤดูกาลนี้", + "tutorial": { + "cpuBenchmarkText": "กวดวิชาทำงานด้วยความเร็วที่น่าหัวเราะ (ทดสอบความเร็วของ CPU เป็นหลัก)", + "phrase01Text": "สวัสดี!", + "phrase02Text": "ยินดีต้อนรับสู่ ${APP_NAME}!", + "phrase03Text": "เคล็ดลับบางประการในการควบคุมตัวละครของคุณมีดังนี้", + "phrase04Text": "หลายสิ่งใน ${APP_NAME} เป็นพื้นฐานทางฟิสิกส์", + "phrase05Text": "เช่น เมื่อคุณต่อย..", + "phrase06Text": "..ความเสียหายขึ้นอยู่กับความเร็วของหมัดของคุณ", + "phrase07Text": "เห็นมั้ย? เราไม่ได้เคลื่อนไหว ดังนั้นมันแทบไม่เจ็บ ${NAME}", + "phrase08Text": "ตอนนี้มากระโดดและหมุนเพื่อรับความเร็วมากขึ้น", + "phrase09Text": "อ่า ดีขึ้นแล้ว", + "phrase10Text": "การวิ่งก็ช่วยได้เช่นกัน", + "phrase11Text": "กดปุ่มใด ๆ ค้างไว้เพื่อวิ่ง", + "phrase12Text": "สำหรับการชกที่ยอดเยี่ยมเป็นพิเศษ ให้ลองวิ่งและหมุน", + "phrase13Text": "อ๊ะ; ขอโทษด้วย ${NAME}", + "phrase14Text": "คุณสามารถหยิบและโยนสิ่งของต่างๆ เช่น ธง.. หรือ ${NAME}", + "phrase15Text": "สุดท้ายมีระเบิด", + "phrase16Text": "การขว้างระเบิดต้องฝึกฝน", + "phrase17Text": "อุ๊ย! โยนไม่ค่อยดี", + "phrase18Text": "การเคลื่อนไหวช่วยให้คุณขว้างได้ไกลขึ้น", + "phrase19Text": "การกระโดดช่วยให้คุณขว้างได้สูงขึ้น", + "phrase20Text": "\"แส้\" ระเบิดของคุณสำหรับการขว้างอีกต่อไป", + "phrase21Text": "การกำหนดเวลาระเบิดของคุณอาจเป็นเรื่องยาก", + "phrase22Text": "แย่ล่ะ", + "phrase23Text": "ลอง \"หุง\" ฟิวส์สักสองสามวินาที", + "phrase24Text": "ไชโย! ปรุงอย่างดี", + "phrase25Text": "นั่นเป็นเพียงเรื่องเกี่ยวกับมัน", + "phrase26Text": "ไปหามันเดี๋ยวนี้ เสือ!", + "phrase27Text": "จำการฝึกของคุณไว้ แล้วคุณจะกลับมามีชีวิตอีกครั้ง!", + "phrase28Text": "...ก็อาจจะ...", + "phrase29Text": "ขอให้โชคดี!", + "randomName1Text": "เฟรด", + "randomName2Text": "แฮร์รี่", + "randomName3Text": "บิล", + "randomName4Text": "ชัค", + "randomName5Text": "ฟิล", + "skipConfirmText": "ข้ามบทช่วยสอนจริงๆหรอ? แตะหรือกดเพื่อยืนยัน", + "skipVoteCountText": "ข้ามโหวท ${COUNT}/${TOTAL} คน", + "skippingText": "ข้ามบทช่วยสอน...", + "toSkipPressAnythingText": "(แตะหรือกดอะไรก็ได้เพื่อข้ามบทแนะนำ)" + }, + "twoKillText": "ดับเบิ้ลคิล!", + "unavailableText": "ไม่พร้อมใช้งาน", + "unconfiguredControllerDetectedText": "ตรวจพบคอนโทรลเลอร์ที่ไม่ได้กำหนดค่า:", + "unlockThisInTheStoreText": "สิ่งนี้จะต้องปลดล็อคในร้านค้า", + "unlockThisProfilesText": "ในการสร้างมากกว่า ${NUM} โปรไฟล์ คุณต้อง:", + "unlockThisText": "เพื่อปลดล็อกสิ่งนี้ คุณต้อง:", + "unsupportedHardwareText": "ขออภัย ฮาร์ดแวร์นี้ไม่ได้รับการสนับสนุนโดยบิวด์ของเกมนี้", + "upFirstText": "ขึ้นก่อน:", + "upNextText": "ต่อไปในเกม ${COUNT}:", + "updatingAccountText": "กำลังอัปเดตบัญชีของคุณ...", + "upgradeText": "อัพเกรด", + "upgradeToPlayText": "ปลดล็อก \"${PRO}\" ในร้านค้าในเกมเพื่อเล่นเกมนี้", + "useDefaultText": "ใช้ค่าเริ่มต้น", + "usesExternalControllerText": "เกมนี้ใช้คอนโทรลเลอร์ภายนอกสำหรับการป้อนข้อมูล", + "usingItunesText": "การใช้แอพ Music สำหรับซาวด์แทร็ก...", + "validatingTestBuildText": "กำลังตรวจสอบการสร้างการทดสอบ...", + "victoryText": "ชัยชนะ!", + "voteDelayText": "คุณไม่สามารถเริ่มโหวตได้อีกเป็นเวลา ${NUMBER} วินาที", + "voteInProgressText": "กำลังดำเนินการโหวทอยู่แล้ว", + "votedAlreadyText": "คุณโหวตแล้ว", + "votesNeededText": "ต้องการ ${NUMBER} คะแนน", + "vsText": "vs.", + "waitingForHostText": "(กำลังรอ ${HOST} ดำเนินการต่อ)", + "waitingForPlayersText": "รอผู้เล่นเข้าร่วม...", + "waitingInLineText": "เข้าแถวรอ(ปาร์ตี้เต็ม)...", + "watchAVideoText": "ดูวิดีโอ", + "watchAnAdText": "ดูโฆษณา", + "watchWindow": { + "deleteConfirmText": "ลบ \"${REPLAY}\" ไหม", + "deleteReplayButtonText": "ลบ\nรีเพลย์", + "myReplaysText": "รีเพลย์ของฉัน", + "noReplaySelectedErrorText": "ไม่ได้เลือกรีเพลย์", + "playbackSpeedText": "ความเร็วในการเล่น: ${SPEED}", + "renameReplayButtonText": "เปลี่ยนชื่อ\nรีเพลย์", + "renameReplayText": "เปลี่ยนชื่อ \"${REPLAY}\" เป็น:", + "renameText": "เปลี่ยนชื่อ", + "replayDeleteErrorText": "เกิดข้อผิดพลาดในการลบรีเพลย์", + "replayNameText": "ชื่อรีเพลย์", + "replayRenameErrorAlreadyExistsText": "มีรีเพลย์ที่มีชื่อนั้นอยู่แล้ว", + "replayRenameErrorInvalidName": "ไม่สามารถเปลี่ยนชื่อรีเพลย์ได้ ชื่อไม่ถูกต้อง.", + "replayRenameErrorText": "เกิดข้อผิดพลาดในการเปลี่ยนชื่อรีเพลย์", + "sharedReplaysText": "แชร์รีเพลย์", + "titleText": "ดู", + "watchReplayButtonText": "ดู\nรีเพลย์" + }, + "waveText": "เวฟ", + "wellSureText": "แน่นอน!", + "wiimoteLicenseWindow": { + "titleText": "ลิขสิทธิ์ DarwiinRemote" + }, + "wiimoteListenWindow": { + "listeningText": "กำลังฟังสำหรับ Wiimotes...", + "pressText": "กดปุ่ม Wiimote 1 และ 2 พร้อมกัน", + "pressText2": "สำหรับ Wiimotes รุ่นใหม่ที่มี Motion Plus ในตัว ให้กดปุ่ม 'ซิงค์' สีแดงที่ด้านหลังแทน" + }, + "wiimoteSetupWindow": { + "copyrightText": "ลิขสิทธิ์ DarwiinRemote", + "listenText": "ฟัง", + "macInstructionsText": "ตรวจสอบให้แน่ใจว่า Wii ของคุณปิดอยู่และเปิดใช้งาน Bluetooth แล้ว\nบน Mac ของคุณ จากนั้นกด 'ฟัง' รองรับ Wiimote ได้\nเป็นขุยๆ หน่อย ต้องลองสักครั้ง\nก่อนที่คุณจะได้รับการเชื่อมต่อ\n\nBluetooth ควรรองรับอุปกรณ์ที่เชื่อมต่อถึง 7 เครื่อง\nแม้ว่าระยะทางของคุณอาจแตกต่างกันไป\n\nBombSquad รองรับ Wiimotes, Nunchuks,\nและตัวควบคุมแบบคลาสสิก\nตอนนี้ Wii Remote Plus ที่ใหม่กว่าก็ใช้งานได้เช่นกัน\nแต่ไม่มีไฟล์แนบ", + "thanksText": "ขอขอบคุณทีมงาน DarwiinRemote\nเพื่อทำให้สิ่งนี้เป็นไปได้", + "titleText": "การตั้งค่า Wiimote" + }, + "winsPlayerText": "${NAME} ชนะ!", + "winsTeamText": "${NAME} ชนะ!", + "winsText": "${NAME} ชนะ!", + "worldScoresUnavailableText": "ไม่มีคะแนนโลก", + "worldsBestScoresText": "คะแนนที่ดีที่สุดในโลก", + "worldsBestTimesText": "เวลาที่ดีที่สุดในโลก", + "xbox360ControllersWindow": { + "getDriverText": "รับไดรเวอร์", + "macInstructions2Text": "หากต้องการใช้คอนโทรลเลอร์แบบไร้สาย คุณจะต้องมีเครื่องรับที่\nมาพร้อมกับ 'Xbox 360 Wireless Controller สำหรับ Windows'\nตัวรับสัญญาณหนึ่งตัวช่วยให้คุณเชื่อมต่อคอนโทรลเลอร์ได้สูงสุด 4 ตัว\n\nสำคัญ: เครื่องรับบุคคลที่สามจะไม่ทำงานกับไดรเวอร์นี้\nตรวจสอบให้แน่ใจว่าผู้รับของคุณระบุว่า 'Microsoft' ไม่ใช่ 'XBOX 360'\nMicrosoft ไม่จำหน่ายแยกต่างหากอีกต่อไป ดังนั้นคุณจะต้องซื้อ\nอันที่มาพร้อมกับคอนโทรลเลอร์หรือค้นหา ebay\n\nหากคุณพบว่าสิ่งนี้มีประโยชน์ โปรดพิจารณาบริจาคให้กับ\nผู้พัฒนาไดรเวอร์ที่ไซต์ของเขา", + "macInstructionsText": "ในการใช้คอนโทรลเลอร์ Xbox 360 คุณจะต้องติดตั้ง\nไดรเวอร์ Mac มีอยู่ที่ลิงค์ด้านล่าง\nใช้งานได้กับทั้งคอนโทรลเลอร์แบบมีสายและไร้สาย", + "ouyaInstructionsText": "หากต้องการใช้สายควบคุม Xbox 360 ด้วย BombSquad เพียง\nเสียบเข้ากับพอร์ต USB ของอุปกรณ์ของคุณ คุณสามารถใช้ฮับ USB\nเพื่อเชื่อมต่อคอนโทรลเลอร์หลายตัว\nในการใช้ตัวควบคุมไร้สายคุณจะต้องมีตัวรับสัญญาณไร้สาย\nเป็นส่วนหนึ่งของ \"Xbox 360 คอนโทรลเลอร์ไร้สายสำหรับ Windows\"\nแพคเกจหรือขายแยกต่างหาก ตัวรับสัญญาณแต่ละตัวจะเสียบเข้ากับพอร์ต USB และ\nช่วยให้คุณเชื่อมต่อคอนโทรลเลอร์ไร้สายได้สูงสุด 4 ตัว", + "titleText": "การใช้ตัวควบคุม Xbox 360 กับ ${APP_NAME}:" + }, + "yesAllowText": "ใช่อนุญาต!", + "yourBestScoresText": "คะแนนที่ดีที่สุดของคุณ", + "yourBestTimesText": "เวลาที่ดีที่สุดของคุณ" +} \ No newline at end of file diff --git a/dist/ba_data/data/languages/turkish.json b/dist/ba_data/data/languages/turkish.json index 34c8f5c..adde109 100644 --- a/dist/ba_data/data/languages/turkish.json +++ b/dist/ba_data/data/languages/turkish.json @@ -817,6 +817,7 @@ "bombInfoText": "- Bomba -\nYumruktan daha güçlüdür, fakat\nkendi bomban ile zarar görebilirsin.\nEn iyi sonuç için fitil bitmeden önce\ndüşmanlarına doğru fırlat.", "canHelpText": "${APP_NAME} sana yardımcı olabilir.", "controllersInfoText": "Arkadaşlarınla birlikte ağ üzerinden veya yeterince kontrolcünüz\nvar ise tek bir cihazdan ${APP_NAME} oynayabilirsiniz.\nHatta telefonunuzu bile bedava '${REMOTE_APP_NAME}' uygulaması ile\nkontrolcü olarak kullanabilirsiniz. ${APP_NAME} bunu destekler.\nDaha fazla bilgi için Ayarlar->Kontroller'e bakın.", + "controllersInfoTextRemoteOnly": "${APP_NAME} 'ı internet üzerinden arkadaşlarınla, ya da telefonları kullanarak aynı cihaz\nüzerinden\n'${REMOTE_APP_NAME}' uygulamasını kullanarak oynayabilirsin.", "controllersText": "Kontrolcüler", "controlsSubtitleText": "Dost Canlısı ${APP_NAME} karakterinin birkaç basit özelliği var:", "controlsText": "Kontroller", @@ -1515,6 +1516,7 @@ "Slovak": "Slovakça", "Spanish": "İspanyolca", "Swedish": "İsveççe", + "Thai": "Tayland dili", "Turkish": "Türkçe", "Ukrainian": "Ukrayna", "Venetian": "Venedik", @@ -1563,6 +1565,7 @@ "Account linking successful!": "Hesap bağlama başarılı!", "Account unlinking successful!": "Hesap bağlantısı başarıyla kesildi!", "Accounts are already linked.": "Hesaplar zaten bağlı.", + "Ad view could not be verified.\nPlease be sure you are running an official and up-to-date version of the game.": "Reklam görünümü doğrulanamadı.\nLütfen oyunun resmi ve güncel bir sürümünü çalıştırdığınızdan emin olun.", "An error has occurred; (${ERROR})": "Bir hata oluştu; (${ERROR})", "An error has occurred; please contact support. (${ERROR})": "Bir hata oluştu; Lütfen destek ile iletişime geçin. (${ERROR})", "An error has occurred; please contact support@froemling.net.": "Bir hata meydana geldi; lütfen support@froemling.net ile iletişime geçin.", @@ -1588,6 +1591,7 @@ "Max number of profiles reached.": "Profiller için maksimum sayıya ulaşıldı.", "Maximum friend code rewards reached.": "Arkadaş kodu maksimuma ulaştı.", "Message is too long.": "Mesaj çok uzun.", + "No servers are available. Please try again soon.": "Kullanılabilir sunucu yok. Lütfen kısa süre sonra tekrar deneyin.", "Profile \"${NAME}\" upgraded successfully.": "\"${NAME}\" Profili başarı ile yükseltildi.", "Profile could not be upgraded.": "Profil yükseltilemedi.", "Purchase successful!": "Satın Alma Başarılı!", @@ -1597,6 +1601,7 @@ "Sorry, this code has already been used.": "Üzgünüz, bu kod zaten kullanılmış.", "Sorry, this code has expired.": "Üzgünüz, Bu kodun süresi doldu.", "Sorry, this code only works for new accounts.": "Üzgünüz, bu kod yalnızca yeni hesaplar için çalışır.", + "Still searching for nearby servers; please try again soon.": "Hala yakındaki sunucuları arıyor; lütfen kısa süre sonra tekrar deneyin.", "Temporarily unavailable; please try again later.": "Geçici olarak kullanım dışı; lütfen daha sonra tekrar deneyiniz.", "The tournament ended before you finished.": "Sen bitiremeden turnuva sona erdi.", "This account cannot be unlinked for ${NUM} days.": "Bu hesap, ${NUM} gün boyunca kaldırılamaz.", diff --git a/dist/ba_data/data/languages/ukrainian.json b/dist/ba_data/data/languages/ukrainian.json index c5ea652..1af0ec2 100644 --- a/dist/ba_data/data/languages/ukrainian.json +++ b/dist/ba_data/data/languages/ukrainian.json @@ -818,6 +818,7 @@ "bombInfoText": "- Бомба -\nСильніше ударів, але може привести\nдо смертельних ушкоджень. для\nнайкращих результатів кидати в\nпротивника поки не догорів гніт.", "canHelpText": "${APP_NAME} може допомогти.", "controllersInfoText": "Ви можете грати в ${APP_NAME} з друзями по мережі, або ви всі можете\nграти на одному пристрої, якщо у вас досить контролерів.\n${APP_NAME} підтримує будь-які контролери; можна навіть використовувати телефони\nв якості контролерів через безкоштовний додаток '${REMOTE_APP_NAME}.\nДивіться Налаштування->Контролери для отримання додаткової інформації.", + "controllersInfoTextRemoteOnly": "Ви можете грати в ${APP_NAME} з друзями по інтернету, або ви\nможете усі грати на одному девайсі якщо використовуєте смартфони як\nконтролери вони безкоштовні '${REMOTE_APP_NAME}' app.", "controllersText": "Контролери", "controlsSubtitleText": "У вашого дружнього персонажа з ${APP_NAME} є кілька простих дій:", "controlsText": "Управління", @@ -1515,6 +1516,7 @@ "Slovak": "Словацька", "Spanish": "Іспанська", "Swedish": "Шведська", + "Thai": "Тайська", "Turkish": "Турецька", "Ukrainian": "Українська", "Venetian": "Венеціанський", @@ -1563,6 +1565,7 @@ "Account linking successful!": "Акаунт успішно прив'язаний!", "Account unlinking successful!": "Акаунт роз'єднаному успішно!", "Accounts are already linked.": "Акаунти вже прив'язані.", + "Ad view could not be verified.\nPlease be sure you are running an official and up-to-date version of the game.": "Не вдалося перевірити перегляд оголошення. \nБудь ласка, переконайтеся, що у вас запущена офіційна та остання версія гри.", "An error has occurred; (${ERROR})": "Сталася помилка; (${ERROR})", "An error has occurred; please contact support. (${ERROR})": "Сталася помилка; будь ласка, зв'яжіться зі службою підтримки (${ERROR})", "An error has occurred; please contact support@froemling.net.": "Сталася помилка; будь ласка, зв'яжіться з support@froemling.net.", @@ -1588,6 +1591,7 @@ "Max number of profiles reached.": "Максимальна кількість профілів досягнута.", "Maximum friend code rewards reached.": "Досягнуто ліміт кодів", "Message is too long.": "Повідомлення занадто довге.", + "No servers are available. Please try again soon.": "Немає доступних серверів. Будь ласка, поспробуйте пізніше.", "Profile \"${NAME}\" upgraded successfully.": "Профіль \"${NAME}\" оновлений успішно.", "Profile could not be upgraded.": "Профіль не може бути оновлений.", "Purchase successful!": "Успішна транзакція!", @@ -1597,6 +1601,7 @@ "Sorry, this code has already been used.": "Ой, цей код вже використаний.", "Sorry, this code has expired.": "Ой, час дії коду минув.", "Sorry, this code only works for new accounts.": "Ой, цей код працює тільки для нових акаунтів.", + "Still searching for nearby servers; please try again soon.": "Все ще йде пошук сусідніх серверів; будь-ласка, спробуйте ще раз найближчим часом.", "Temporarily unavailable; please try again later.": "Тимчасово недоступний; будь ласка, спробуйте ще раз пізніше.", "The tournament ended before you finished.": "Турнір закінчився раніше, ніж ви закінчили.", "This account cannot be unlinked for ${NUM} days.": "Цей обліковий запис неможливо відв'язати протягом ${NUM} днів.", diff --git a/dist/ba_data/data/languages/venetian.json b/dist/ba_data/data/languages/venetian.json index fe1681f..b300229 100644 --- a/dist/ba_data/data/languages/venetian.json +++ b/dist/ba_data/data/languages/venetian.json @@ -814,6 +814,7 @@ "bombInfoText": "- Bonbe -\nPì forti de i crogni, ma łe połe\nfarte małe anca a ti. Dopàrełe\nben tràndoghełe doso a i nemighi\nprima che łe salte par aria.", "canHelpText": "${APP_NAME} el połe jutarte!", "controllersInfoText": "A te połi zugar a ${APP_NAME} co i amighi co na rede o, se gavì\ncontroładori che basta, połì zugar tuti insenbre so el mèdemo\ndispozitivo: ${APP_NAME} el ghin suporta racuanti. Połì senpre doparar\ni tełèfoni cofà controładori co l’apl gratùida '${REMOTE_APP_NAME}'.\nPar info in pì varda so Inpostasion > Controładori.", + "controllersInfoTextRemoteOnly": "Te połi zugar a ${APP_NAME} co i to amighi doparando na rede,\no zugar tuti so'l mèdemo dispozitivo doparando i tełèfoni cofà\ncontroładori co l'apl gratùida '${REMOTE_APP_NAME}'.", "controllersText": "Controładori", "controlsSubtitleText": "El to amighévołe parsonajo de ${APP_NAME} el gà un fià de funsion de baze:", "controlsText": "Controłi", @@ -1132,7 +1133,7 @@ "purchasingText": "Cronpa in corso...", "quitGameText": "Vutu sortir da ${APP_NAME}?", "quittingIn5SecondsText": "Sortìa tenpo 5 segondi...", - "randomPlayerNamesText": "Toni, Bepi, Ciano, Ico, Tisio, Senpronio, Cajo, Ciorci, Ucio, Tòio, Stełio, Duiłio, Mariza, Sunta, Tano, Tilde, Piero, Neno, Nena, Momi, Ménego, Łełe, Jani, Jaco, Cicio, Giza, Checo, Bice, Beta, Gneze, Àndoło, Jijo, Aneta, Bórtoło, Metrio, Gidio, Gnegno, Baldi, Icio, Nane, Tano, Jema, Maria", + "randomPlayerNamesText": "Toni, Bepi, Ciano, Ico, Tisio, Senpronio, Cajo, Ciorci, Ucio, Tòio, Stełio, Duiłio, Mariza, Sunta, Tano, Tilde, Piero, Neno, Nena, Momi, Ménego, Łełe, Jani, Jaco, Cicio, Giza, Checo, Bice, Beta, Gneze, Àndoło, Jijo, Aneta, Bórtoło, Metrio, Gidio, Gnegno, Baldi, Icio, Nane, Tano, Jema, Maria, Zezi, Giobata, Marco, Nicołò, Orsoła, Fosca, Zeno, Almorò, Zuan, Poło, Bazejo, Tòdaro, Micel, Zanze, Elvira, Zita, Dora, Noemi, Irma, Elda, Stae, Mirałio, Zerlin, Dardi, Ełide", "randomText": "A stin", "rankText": "Pozision", "ratingText": "Vałudasion", @@ -1511,6 +1512,7 @@ "Slovak": "Zlovaco", "Spanish": "Spagnoło", "Swedish": "Zvedeze", + "Thai": "Thailandeze", "Turkish": "Turco", "Ukrainian": "Ucrain", "Venetian": "Veneto", @@ -1559,6 +1561,7 @@ "Account linking successful!": "Profiło cołegà co suceso!", "Account unlinking successful!": "Profiło descołegà co suceso!", "Accounts are already linked.": "I profiłi i ze dezà cołegài.", + "Ad view could not be verified.\nPlease be sure you are running an official and up-to-date version of the game.": "No ze stà posìbiłe verifegar che te ghè vardà ła reclan.\nSegùrate de èsar drio doparar na varsion de'l zugo ofisiałe e ajornada.", "An error has occurred; (${ERROR})": "A se gà verifegà un eror: (${ERROR})", "An error has occurred; please contact support. (${ERROR})": "A se gà verifegà un eror: contata l'asistensa. (${ERROR})", "An error has occurred; please contact support@froemling.net.": "A se gà verifegà un eror: contata support@froemling.net.", @@ -1584,6 +1587,7 @@ "Max number of profiles reached.": "Nùmaro màsemo de profiłi pasà.", "Maximum friend code rewards reached.": "Brincà el nùmaro màsemo de premi da'l còdaze amigo.", "Message is too long.": "Mesajo masa łongo.", + "No servers are available. Please try again soon.": "Gnaun server disponìbiłe. Proa pì tardi.", "Profile \"${NAME}\" upgraded successfully.": "Profiło \"${NAME}\" mejorà co suceso.", "Profile could not be upgraded.": "El profiło no'l połe mìa èsar mejorà.", "Purchase successful!": "Cronpà co suceso!", @@ -1593,6 +1597,7 @@ "Sorry, this code has already been used.": "Ne despiaze, 'sto còdaze el ze dezà stà doparà.", "Sorry, this code has expired.": "Ne despiaze, ła vałidità de 'sto còdaze ła ze terminada.", "Sorry, this code only works for new accounts.": "Ne despiaze, 'sto còdaze el funsiona soło so i account novi.", + "Still searching for nearby servers; please try again soon.": "Reserca de server visini in corso: proa danovo pì tardi.", "Temporarily unavailable; please try again later.": "Par deso miga disponìbiłe: proa danovo pì tardi.", "The tournament ended before you finished.": "El tornèo el ze terminà prima che te ghesi fenìo.", "This account cannot be unlinked for ${NUM} days.": "'Sto account no'l połe mìa èsar descołegà prima de ${NUM} dì.", diff --git a/dist/ba_data/data/languages/vietnamese.json b/dist/ba_data/data/languages/vietnamese.json index 6d8bd23..9dc7444 100644 --- a/dist/ba_data/data/languages/vietnamese.json +++ b/dist/ba_data/data/languages/vietnamese.json @@ -751,10 +751,19 @@ "pingText": "ping", "portText": "Cổng", "privatePartyCloudDescriptionText": "Các phòng riêng chạy trên các máy chủ trên mây chuyên dụng; không cần cấu hình bộ định tuyến", + "privatePartyHostText": "Làm chủ 1 phòng riêng", + "privatePartyJoinText": "Tham gia 1 phòng riêng", + "privateText": "Riêng tư", + "publicHostRouterConfigText": "Điều này có thể yêu cầu định cấu hình chuyển tiếp cổng trên bộ định tuyến của bạn. Để có một lựa chọn dễ dàng hơn, hãy tổ chức một phòng riêng.", + "publicText": "Công khai", "requestingAPromoCodeText": "Yêu cầu mã...", "sendDirectInvitesText": "Gửi trực tiếp lời mời", "shareThisCodeWithFriendsText": "Chia sẻ mã này với bạn bè:", "showMyAddressText": "Hiển thị địa chỉ", + "startHostingPaidText": "Làm chủ phòng ngay bây giờ chỉ ${COST}", + "startHostingText": "Làm chủ phòng", + "startStopHostingMinutesText": "Bạn có thể bắt đầu và dừng làm chủ phòng MIỄN PHÍ trong ${MINUTES} phút tiếp theo", + "stopHostingText": "Dừng làm chủ phòng", "titleText": "Nhiều Người", "wifiDirectDescriptionBottomText": "Nếu tất cả các thiết bị có bảng điều khiển 'Wi-Fi Direct', họ sẽ có thể sử dụng nó để tìm\n và kết nối với nhau. Khi tất cả các thiết bị được kết nối, bạn có thể tạo thành các bữa tiệc\n ở đây sử dụng tab 'Mạng cục bộ', giống như với mạng Wi-Fi thông thường.\n\n Để có kết quả tốt nhất, máy chủ Wi-Fi Direct cũng phải là máy chủ của bữa tiệc ${APP_NAME}.", "wifiDirectDescriptionTopText": "Wi-Fi Direct có thể được sử dụng để kết nối trực tiếp các thiết bị Android mà không cần\n một mạng Wi-Fi. Điều này hoạt động tốt nhất trên Android 4.2 trở lên.\n\n Để sử dụng nó, hãy mở cài đặt Wi-Fi và tìm 'Wi-Fi Direct' trong menu.", @@ -808,9 +817,10 @@ "visualsText": "Hình ảnh" }, "helpWindow": { - "bombInfoText": "- Bom -\nMạnh hơn nắm đấm, nhưng\nbạn có thể tự làm hại mình.\nĐể có kết quả tốt nhất, ném\nvề phía đói phương trước khi nổ", - "canHelpText": "${APP_NAME} Có thể giúp ban.", + "bombInfoText": "- Bom -\nMạnh hơn nắm đấm, nhưng\nbạn có thể tự làm hại mình.\nĐể có kết quả tốt nhất, ném\nvề phía đối phương trước khi nổ.", + "canHelpText": "${APP_NAME} Có thể giúp bạn.", "controllersInfoText": "Bạn có thể chơi ${APP_NAME} với bạn bè hay qua mạng, hoặc tất\ncả các bạn có thể chơi trên một thiết bị nếu đủ bộ điều khiển.\n${APP_NAME} hổ trợ một lượng lớn các thiết bị; bạn có thể sử dụng\nđiện thoại như một bộ điều khiển qua ứng dụng '${REMOTE_APP_NAME}'.\nĐến Cài Đặt -> bộ điều khiển để xem thêm.", + "controllersInfoTextRemoteOnly": "Chơi ${APP_NAME} với bạn bè \nqua mạng, hoặc thiết bị như điện thoại qua ứng dụng \n'${REMOTE_APP_NAME}'.", "controllersText": "Bộ điều khiển", "controlsSubtitleText": "Nhân vật ${APP_NAME} của bạn có một vài động tác cơ bản:", "controlsText": "Điều khiển", @@ -818,7 +828,7 @@ "devicesText": "Thiết bị", "friendsGoodText": "Rất tốt nếu bạn có. ${APP_NAME} vui hơn khi có nhiều người chơi\nhơn và có thể hỗ trợ lên đến 8 người chơi cùng một lúc, Vì vậy:", "friendsText": "Bạn bè", - "jumpInfoText": "- Nhảy -\nNhảy qua các chướng ngại vật,\nđể ném cao hơn, và cảm nhận\nđược niềm vi của bay lượn.", + "jumpInfoText": "- Nhảy -\nNhảy qua các chướng ngại vật,\nđể ném cao hơn, và cảm nhận\nđược niềm vui của bay lượn.", "orPunchingSomethingText": "Hoặc đấm một vật nào đó, ném nó xuống vực, và làm nổ tung nó khi nó bay xuống bằng bom dính.", "pickUpInfoText": "- Nhặt -\nNhặt cờ, đối thủ, hoặc bất cứ\nthứ gì khác không dính vào sàn.\nNhấn lại để ném.", "powerupBombDescriptionText": "Cho phép bạn ném ba bom cùng\nmột lúc thay vì chỉ một.", @@ -827,7 +837,7 @@ "powerupCurseNameText": "Lời Nguyền", "powerupHealthDescriptionText": "Hồi đầy máu cho bạn.\nKhông cần phải lo lắng.", "powerupHealthNameText": "Hộp Cứu Thương", - "powerupIceBombsDescriptionText": "Yếu hon bom cơ bản nhưng\nlàm kẻ thù đóng băng và\nhoàn toàn bất động.", + "powerupIceBombsDescriptionText": "Yếu hơn bom cơ bản nhưng\nlàm kẻ thù đóng băng và\nhoàn toàn bất động.", "powerupIceBombsNameText": "Bom Băng Giá", "powerupImpactBombsDescriptionText": "Yếu hơn bom cơ bản một chút, nhưng\nnó có thể nổ ngay khi chạm vào.", "powerupImpactBombsNameText": "Bom Hạt Nhân", @@ -840,7 +850,7 @@ "powerupStickyBombsDescriptionText": "Dính vào mọi thứ nó chạm.\nHoàn toàn chắc chắn.", "powerupStickyBombsNameText": "Bom dính", "powerupsSubtitleText": "Dĩ nhiên, không trò chơi nào có thể thiếu :", - "powerupsText": "Năng Lục", + "powerupsText": "Năng Lực", "punchInfoText": "- Đấm -\nCú đấm gây ra nhiều sát thương hơn\nkhi bạn đang di chuyển, nên hãy\ncứ chạy và xoay như kẻ điên.", "runInfoText": "- Chạy -\nGiữ BẤT KÌ nút nào để chạy. Các nút khác đều hoạt động nếu bạn có nó.\nChạy giúp bạn di chuyển nhanh hơn nhưng cũng làm bạn khó điều khiển hơn.", "someDaysText": "Một ngày bạn muốn đấm thứ gì đó. Hoặc làm nổ tung nó.", @@ -856,7 +866,7 @@ "importText": "Xuất", "importingText": "Đang nhập...", "inGameClippedNameText": "trong game sẽ là \n\"${NAME}\"", - "installDiskSpaceErrorText": "Lỗi: Không thể hoàn thành tải xuống.\nBạn có thể hết dung lượng trên thiết bị của bạn.\nDọn một ít chỗ trống và thử lại.", + "installDiskSpaceErrorText": "Lỗi: Không thể hoàn thành tải xuống.\nBạn có thể hết dung lượng trên thiết bị của bạn.\nDọn một số chỗ và thử lại.", "internal": { "arrowsToExitListText": "nhấn ${LEFT} hoặc ${RIGHT} để rời khỏi danh sách", "buttonText": "Nút", @@ -1002,6 +1012,9 @@ "maxConnectionsText": "Kết nối tối đa", "maxPartySizeText": "Số người trong bữa tiệc tối đa", "maxPlayersText": "Người chơi tối đa", + "modeArcadeText": "Chế độ giải trí", + "modeClassicText": "Chế độ cổ điển", + "modeDemoText": "Chế độ thử nghiệm", "mostValuablePlayerText": "Người chơi đáng giá nhất", "mostViolatedPlayerText": "Người chơi bị chết nhiều nhất", "mostViolentPlayerText": "Người chơi bạo lực nhất", @@ -1043,6 +1056,7 @@ "offText": "Tắt", "okText": "Ok", "onText": "Bật", + "oneMomentText": "Một lúc...", "onslaughtRespawnText": "${PLAYER} sẽ hồi sinh trong ải ${WAVE}", "orText": "${A} hoặc ${B}", "otherText": "Khác ...", @@ -1089,6 +1103,7 @@ "playerText": "Người chơi", "playlistNoValidGamesErrorText": "Danh sách này không chứa trò chơi nào đã mở khóa.", "playlistNotFoundText": "không tìm thấy danh sách", + "playlistText": "Danh sách chế độ chơi", "playlistsText": "Danh sách", "pleaseRateText": "Nếu bạn thấy ${APP_NAME} vui lòng \nđánh giá hoặc viết \ncảm nhận.\nĐiều này giúp hỗ trợ phát triển trong tương lai.\ncảm ơn!\n-eric", "pleaseWaitText": "Vui lòng chờ...", @@ -1503,8 +1518,10 @@ "Slovak": "Tiếng Slovakia", "Spanish": "Tiếng Tây Ban Nha", "Swedish": "Tiếng Thụy Điển", + "Thai": "Tiếng thái", "Turkish": "Tiếng Thổ Nhĩ Kỳ", "Ukrainian": "Tiếng Ukraina", + "Venetian": "Tiếng Venice", "Vietnamese": "Tiếng Việt" }, "leagueNames": { @@ -1550,6 +1567,7 @@ "Account linking successful!": "Liên kết tài khoản hoàn tất!", "Account unlinking successful!": "Hủy liên kết tài khoản hoàn tất!", "Accounts are already linked.": "Tài khoản đã liên kết.", + "Ad view could not be verified.\nPlease be sure you are running an official and up-to-date version of the game.": "Không thể xác minh chế độ xem quảng cáo.\nHãy chắc chắn rằng bạn đang chạy phiên bản chính thức và cập nhật của trò chơi.", "An error has occurred; (${ERROR})": "Lỗi xảy ra; (${ERROR})", "An error has occurred; please contact support. (${ERROR})": "Một lỗi đã xảy ra; vui lòng liên hệ bộ phận hỗ trợ. (${ERROR})", "An error has occurred; please contact support@froemling.net.": "Lỗi xảy ra; vui lòng báo cáo tại support@froemling.net.", @@ -1575,6 +1593,7 @@ "Max number of profiles reached.": "Đã đạt tối đa số lượng hồ sơ.", "Maximum friend code rewards reached.": "Đã đạt tối đa thưởng từ mã code bạn bè.", "Message is too long.": "Tin nhắn quá dài.", + "No servers are available. Please try again soon.": "Không có server có sẵn. Hãy thử lại sau.", "Profile \"${NAME}\" upgraded successfully.": "Hồ sơ \"${NAME}\" nâng cấp thành công.", "Profile could not be upgraded.": "Hồ sơ không thể nâng cấp.", "Purchase successful!": "Mua thành công!", @@ -1584,10 +1603,12 @@ "Sorry, this code has already been used.": "Xin lỗi bạn, mã code này đã đc sử dụng", "Sorry, this code has expired.": "Xin lỗi, mã code này đã hết hạn.", "Sorry, this code only works for new accounts.": "Xin lỗi,mã code này chỉ dành cho người chơi mới.", + "Still searching for nearby servers; please try again soon.": "Vẫn đang tìm những server gần nhất; Hãy thử lại sau", "Temporarily unavailable; please try again later.": "Tạm thời không có; vui lòng thử lại sau.", "The tournament ended before you finished.": "Giải đấu đã kết thúc trước khi bạn hoàn thành.", "This account cannot be unlinked for ${NUM} days.": "Tài khoản này không thể ngừng liên kết trong ${NUM} ngày.", "This code cannot be used on the account that created it.": "Mã code này không thể sử dụng đc ở tài khoản đã tạo ra nó.", + "This is currently unavailable; please try again later.": "Hiền không có sẵn; hãy thử lại sau.", "This requires version ${VERSION} or newer.": "Yêu cầu phiên bản ${VERSION} hoặc mới hơn.", "Tournaments disabled due to rooted device.": "Giải đấu đã tắt bởi vì thiết bị đã root.", "Tournaments require ${VERSION} or newer": "Giải đấu yêu cầu phiên bản ${VERSION} hoặc mới hơn.", diff --git a/dist/ba_data/python-site-packages/__pycache__/typing_extensions.cpython-38.opt-1.pyc b/dist/ba_data/python-site-packages/__pycache__/typing_extensions.cpython-38.opt-1.pyc index 7abfad5..4e28724 100644 Binary files a/dist/ba_data/python-site-packages/__pycache__/typing_extensions.cpython-38.opt-1.pyc and b/dist/ba_data/python-site-packages/__pycache__/typing_extensions.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python-site-packages/__pycache__/typing_extensions.cpython-39.opt-1.pyc b/dist/ba_data/python-site-packages/__pycache__/typing_extensions.cpython-39.opt-1.pyc new file mode 100644 index 0000000..04a382a Binary files /dev/null and b/dist/ba_data/python-site-packages/__pycache__/typing_extensions.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python-site-packages/_yaml/__init__.py b/dist/ba_data/python-site-packages/_yaml/__init__.py new file mode 100644 index 0000000..7baa8c4 --- /dev/null +++ b/dist/ba_data/python-site-packages/_yaml/__init__.py @@ -0,0 +1,33 @@ +# This is a stub package designed to roughly emulate the _yaml +# extension module, which previously existed as a standalone module +# and has been moved into the `yaml` package namespace. +# It does not perfectly mimic its old counterpart, but should get +# close enough for anyone who's relying on it even when they shouldn't. +import yaml + +# in some circumstances, the yaml module we imoprted may be from a different version, so we need +# to tread carefully when poking at it here (it may not have the attributes we expect) +if not getattr(yaml, '__with_libyaml__', False): + from sys import version_info + + exc = ModuleNotFoundError if version_info >= (3, 6) else ImportError + raise exc("No module named '_yaml'") +else: + from yaml._yaml import * + import warnings + warnings.warn( + 'The _yaml extension module is now located at yaml._yaml' + ' and its location is subject to change. To use the' + ' LibYAML-based parser and emitter, import from `yaml`:' + ' `from yaml import CLoader as Loader, CDumper as Dumper`.', + DeprecationWarning + ) + del warnings + # Don't `del yaml` here because yaml is actually an existing + # namespace member of _yaml. + +__name__ = '_yaml' +# If the module is top-level (i.e. not a part of any specific package) +# then the attribute should be set to ''. +# https://docs.python.org/3.8/library/types.html +__package__ = '' diff --git a/dist/ba_data/python-site-packages/_yaml/__pycache__/__init__.cpython-38.opt-1.pyc b/dist/ba_data/python-site-packages/_yaml/__pycache__/__init__.cpython-38.opt-1.pyc new file mode 100644 index 0000000..aae20c7 Binary files /dev/null and b/dist/ba_data/python-site-packages/_yaml/__pycache__/__init__.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python-site-packages/_yaml/__pycache__/__init__.cpython-39.opt-1.pyc b/dist/ba_data/python-site-packages/_yaml/__pycache__/__init__.cpython-39.opt-1.pyc new file mode 100644 index 0000000..10aa056 Binary files /dev/null and b/dist/ba_data/python-site-packages/_yaml/__pycache__/__init__.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python-site-packages/typing_extensions.py b/dist/ba_data/python-site-packages/typing_extensions.py index a6f4281..95bb873 100644 --- a/dist/ba_data/python-site-packages/typing_extensions.py +++ b/dist/ba_data/python-site-packages/typing_extensions.py @@ -18,6 +18,7 @@ PEP_560 = sys.version_info[:3] >= (3, 7, 0) if PEP_560: GenericMeta = TypingMeta = type + from typing import _GenericAlias else: from typing import GenericMeta, TypingMeta OLD_GENERICS = False @@ -115,7 +116,9 @@ else: __all__ = [ # Super-special typing primitives. 'ClassVar', + 'Concatenate', 'Final', + 'ParamSpec', 'Type', # ABCs (from collections.abc). @@ -134,6 +137,7 @@ __all__ = [ 'Counter', 'Deque', 'DefaultDict', + 'OrderedDict', 'TypedDict', # Structural checks, a.k.a. protocols. @@ -146,6 +150,8 @@ __all__ = [ 'NewType', 'overload', 'Text', + 'TypeAlias', + 'TypeGuard', 'TYPE_CHECKING', ] @@ -938,6 +944,34 @@ else: return _generic_new(collections.defaultdict, cls, *args, **kwds) +if hasattr(typing, 'OrderedDict'): + OrderedDict = typing.OrderedDict +elif (3, 7, 0) <= sys.version_info[:3] < (3, 7, 2): + OrderedDict = typing._alias(collections.OrderedDict, (KT, VT)) +elif _geqv_defined: + class OrderedDict(collections.OrderedDict, typing.MutableMapping[KT, VT], + metaclass=_ExtensionsGenericMeta, + extra=collections.OrderedDict): + + __slots__ = () + + def __new__(cls, *args, **kwds): + if _geqv(cls, OrderedDict): + return collections.OrderedDict(*args, **kwds) + return _generic_new(collections.OrderedDict, cls, *args, **kwds) +else: + class OrderedDict(collections.OrderedDict, typing.MutableMapping[KT, VT], + metaclass=_ExtensionsGenericMeta, + extra=collections.OrderedDict): + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is OrderedDict: + return collections.OrderedDict(*args, **kwds) + return _generic_new(collections.OrderedDict, cls, *args, **kwds) + + if hasattr(typing, 'Counter'): Counter = typing.Counter elif (3, 5, 0) <= sys.version_info[:3] <= (3, 5, 1): @@ -1119,6 +1153,11 @@ def _is_callable_members_only(cls): if hasattr(typing, 'Protocol'): Protocol = typing.Protocol elif HAVE_PROTOCOLS and not PEP_560: + + def _no_init(self, *args, **kwargs): + if type(self)._is_protocol: + raise TypeError('Protocols cannot be instantiated') + class _ProtocolMeta(GenericMeta): """Internal metaclass for Protocol. @@ -1209,9 +1248,6 @@ elif HAVE_PROTOCOLS and not PEP_560: raise TypeError('Protocols can only inherit from other' ' protocols, got %r' % base) - def _no_init(self, *args, **kwargs): - if type(self)._is_protocol: - raise TypeError('Protocols cannot be instantiated') cls.__init__ = _no_init def _proto_hook(other): @@ -1364,7 +1400,11 @@ elif HAVE_PROTOCOLS and not PEP_560: elif PEP_560: - from typing import _type_check, _GenericAlias, _collect_type_vars # noqa + from typing import _type_check, _collect_type_vars # noqa + + def _no_init(self, *args, **kwargs): + if type(self)._is_protocol: + raise TypeError('Protocols cannot be instantiated') class _ProtocolMeta(abc.ABCMeta): # This metaclass is a bit unfortunate and exists only because of the lack @@ -1542,10 +1582,6 @@ elif PEP_560: isinstance(base, _ProtocolMeta) and base._is_protocol): raise TypeError('Protocols can only inherit from other' ' protocols, got %r' % base) - - def _no_init(self, *args, **kwargs): - if type(self)._is_protocol: - raise TypeError('Protocols cannot be instantiated') cls.__init__ = _no_init @@ -1584,9 +1620,11 @@ elif HAVE_PROTOCOLS: pass -if sys.version_info[:2] >= (3, 9): +if sys.version_info >= (3, 9, 2): # The standard library TypedDict in Python 3.8 does not store runtime information # about which (if any) keys are optional. See https://bugs.python.org/issue38834 + # The standard library TypedDict in Python 3.9.0/1 does not honour the "total" + # keyword with old-style TypedDict(). See https://bugs.python.org/issue42059 TypedDict = typing.TypedDict else: def _check_fails(cls, other): @@ -1643,19 +1681,25 @@ else: raise TypeError("TypedDict takes either a dict or keyword arguments," " but not both") - ns = {'__annotations__': dict(fields), '__total__': total} + ns = {'__annotations__': dict(fields)} try: # Setting correct module is necessary to make typed dict classes pickleable. ns['__module__'] = sys._getframe(1).f_globals.get('__name__', '__main__') except (AttributeError, ValueError): pass - return _TypedDictMeta(typename, (), ns) + return _TypedDictMeta(typename, (), ns, total=total) _typeddict_new.__text_signature__ = ('($cls, _typename, _fields=None,' ' /, *, total=True, **kwargs)') class _TypedDictMeta(type): + def __init__(cls, name, bases, ns, total=True): + # In Python 3.4 and 3.5 the __init__ method also needs to support the + # keyword arguments. + # See https://www.python.org/dev/peps/pep-0487/#implementation-details + super(_TypedDictMeta, cls).__init__(name, bases, ns) + def __new__(cls, name, bases, ns, total=True): # Create new typed dict class object. # This method is called directly when TypedDict is subclassed, @@ -2024,11 +2068,22 @@ elif HAVE_ANNOTATED: # Python 3.8 has get_origin() and get_args() but those implementations aren't # Annotated-aware, so we can't use those, only Python 3.9 versions will do. -if sys.version_info[:2] >= (3, 9): +# Similarly, Python 3.9's implementation doesn't support ParamSpecArgs and +# ParamSpecKwargs. +if sys.version_info[:2] >= (3, 10): get_origin = typing.get_origin get_args = typing.get_args elif PEP_560: - from typing import _GenericAlias # noqa + try: + # 3.9+ + from typing import _BaseGenericAlias + except ImportError: + _BaseGenericAlias = _GenericAlias + try: + # 3.9+ + from typing import GenericAlias + except ImportError: + GenericAlias = _GenericAlias def get_origin(tp): """Get the unsubscripted version of a type. @@ -2043,10 +2098,12 @@ elif PEP_560: get_origin(Generic[T]) is Generic get_origin(Union[T, int]) is Union get_origin(List[Tuple[T, T]][int]) == list + get_origin(P.args) is P """ if isinstance(tp, _AnnotatedAlias): return Annotated - if isinstance(tp, _GenericAlias): + if isinstance(tp, (_GenericAlias, GenericAlias, _BaseGenericAlias, + ParamSpecArgs, ParamSpecKwargs)): return tp.__origin__ if tp is Generic: return Generic @@ -2065,7 +2122,9 @@ elif PEP_560: """ if isinstance(tp, _AnnotatedAlias): return (tp.__origin__,) + tp.__metadata__ - if isinstance(tp, _GenericAlias): + if isinstance(tp, (_GenericAlias, GenericAlias)): + if getattr(tp, "_special", False): + return () res = tp.__args__ if get_origin(tp) is collections.abc.Callable and res[0] is not Ellipsis: res = (list(res[:-1]), res[-1]) @@ -2166,3 +2225,619 @@ else: It's invalid when used anywhere except as in the example above. """ __slots__ = () + + +# Python 3.10+ has PEP 612 +if hasattr(typing, 'ParamSpecArgs'): + ParamSpecArgs = typing.ParamSpecArgs + ParamSpecKwargs = typing.ParamSpecKwargs +else: + class _Immutable: + """Mixin to indicate that object should not be copied.""" + __slots__ = () + + def __copy__(self): + return self + + def __deepcopy__(self, memo): + return self + + class ParamSpecArgs(_Immutable): + """The args for a ParamSpec object. + + Given a ParamSpec object P, P.args is an instance of ParamSpecArgs. + + ParamSpecArgs objects have a reference back to their ParamSpec: + + P.args.__origin__ is P + + This type is meant for runtime introspection and has no special meaning to + static type checkers. + """ + def __init__(self, origin): + self.__origin__ = origin + + def __repr__(self): + return "{}.args".format(self.__origin__.__name__) + + class ParamSpecKwargs(_Immutable): + """The kwargs for a ParamSpec object. + + Given a ParamSpec object P, P.kwargs is an instance of ParamSpecKwargs. + + ParamSpecKwargs objects have a reference back to their ParamSpec: + + P.kwargs.__origin__ is P + + This type is meant for runtime introspection and has no special meaning to + static type checkers. + """ + def __init__(self, origin): + self.__origin__ = origin + + def __repr__(self): + return "{}.kwargs".format(self.__origin__.__name__) + +if hasattr(typing, 'ParamSpec'): + ParamSpec = typing.ParamSpec +else: + + # Inherits from list as a workaround for Callable checks in Python < 3.9.2. + class ParamSpec(list): + """Parameter specification variable. + + Usage:: + + P = ParamSpec('P') + + Parameter specification variables exist primarily for the benefit of static + type checkers. They are used to forward the parameter types of one + callable to another callable, a pattern commonly found in higher order + functions and decorators. They are only valid when used in ``Concatenate``, + or s the first argument to ``Callable``. In Python 3.10 and higher, + they are also supported in user-defined Generics at runtime. + See class Generic for more information on generic types. An + example for annotating a decorator:: + + T = TypeVar('T') + P = ParamSpec('P') + + def add_logging(f: Callable[P, T]) -> Callable[P, T]: + '''A type-safe decorator to add logging to a function.''' + def inner(*args: P.args, **kwargs: P.kwargs) -> T: + logging.info(f'{f.__name__} was called') + return f(*args, **kwargs) + return inner + + @add_logging + def add_two(x: float, y: float) -> float: + '''Add two numbers together.''' + return x + y + + Parameter specification variables defined with covariant=True or + contravariant=True can be used to declare covariant or contravariant + generic types. These keyword arguments are valid, but their actual semantics + are yet to be decided. See PEP 612 for details. + + Parameter specification variables can be introspected. e.g.: + + P.__name__ == 'T' + P.__bound__ == None + P.__covariant__ == False + P.__contravariant__ == False + + Note that only parameter specification variables defined in global scope can + be pickled. + """ + + # Trick Generic __parameters__. + __class__ = TypeVar + + @property + def args(self): + return ParamSpecArgs(self) + + @property + def kwargs(self): + return ParamSpecKwargs(self) + + def __init__(self, name, *, bound=None, covariant=False, contravariant=False): + super().__init__([self]) + self.__name__ = name + self.__covariant__ = bool(covariant) + self.__contravariant__ = bool(contravariant) + if bound: + self.__bound__ = typing._type_check(bound, 'Bound must be a type.') + else: + self.__bound__ = None + + # for pickling: + try: + def_mod = sys._getframe(1).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): + def_mod = None + if def_mod != 'typing_extensions': + self.__module__ = def_mod + + def __repr__(self): + if self.__covariant__: + prefix = '+' + elif self.__contravariant__: + prefix = '-' + else: + prefix = '~' + return prefix + self.__name__ + + def __hash__(self): + return object.__hash__(self) + + def __eq__(self, other): + return self is other + + def __reduce__(self): + return self.__name__ + + # Hack to get typing._type_check to pass. + def __call__(self, *args, **kwargs): + pass + + if not PEP_560: + # Only needed in 3.6 and lower. + def _get_type_vars(self, tvars): + if self not in tvars: + tvars.append(self) + + +# Inherits from list as a workaround for Callable checks in Python < 3.9.2. +class _ConcatenateGenericAlias(list): + + # Trick Generic into looking into this for __parameters__. + if PEP_560: + __class__ = typing._GenericAlias + elif sys.version_info[:3] == (3, 5, 2): + __class__ = typing.TypingMeta + else: + __class__ = typing._TypingBase + + # Flag in 3.8. + _special = False + # Attribute in 3.6 and earlier. + if sys.version_info[:3] == (3, 5, 2): + _gorg = typing.GenericMeta + else: + _gorg = typing.Generic + + def __init__(self, origin, args): + super().__init__(args) + self.__origin__ = origin + self.__args__ = args + + def __repr__(self): + _type_repr = typing._type_repr + return '{origin}[{args}]' \ + .format(origin=_type_repr(self.__origin__), + args=', '.join(_type_repr(arg) for arg in self.__args__)) + + def __hash__(self): + return hash((self.__origin__, self.__args__)) + + # Hack to get typing._type_check to pass in Generic. + def __call__(self, *args, **kwargs): + pass + + @property + def __parameters__(self): + return tuple(tp for tp in self.__args__ if isinstance(tp, (TypeVar, ParamSpec))) + + if not PEP_560: + # Only required in 3.6 and lower. + def _get_type_vars(self, tvars): + if self.__origin__ and self.__parameters__: + typing._get_type_vars(self.__parameters__, tvars) + + +@_tp_cache +def _concatenate_getitem(self, parameters): + if parameters == (): + raise TypeError("Cannot take a Concatenate of no types.") + if not isinstance(parameters, tuple): + parameters = (parameters,) + if not isinstance(parameters[-1], ParamSpec): + raise TypeError("The last parameter to Concatenate should be a " + "ParamSpec variable.") + msg = "Concatenate[arg, ...]: each arg must be a type." + parameters = tuple(typing._type_check(p, msg) for p in parameters) + return _ConcatenateGenericAlias(self, parameters) + + +if hasattr(typing, 'Concatenate'): + Concatenate = typing.Concatenate + _ConcatenateGenericAlias = typing._ConcatenateGenericAlias # noqa +elif sys.version_info[:2] >= (3, 9): + @_TypeAliasForm + def Concatenate(self, parameters): + """Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a + higher order function which adds, removes or transforms parameters of a + callable. + + For example:: + + Callable[Concatenate[int, P], int] + + See PEP 612 for detailed information. + """ + return _concatenate_getitem(self, parameters) + +elif sys.version_info[:2] >= (3, 7): + class _ConcatenateForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + def __getitem__(self, parameters): + return _concatenate_getitem(self, parameters) + + Concatenate = _ConcatenateForm( + 'Concatenate', + doc="""Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a + higher order function which adds, removes or transforms parameters of a + callable. + + For example:: + + Callable[Concatenate[int, P], int] + + See PEP 612 for detailed information. + """) + +elif hasattr(typing, '_FinalTypingBase'): + class _ConcatenateAliasMeta(typing.TypingMeta): + """Metaclass for Concatenate.""" + + def __repr__(self): + return 'typing_extensions.Concatenate' + + class _ConcatenateAliasBase(typing._FinalTypingBase, + metaclass=_ConcatenateAliasMeta, + _root=True): + """Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a + higher order function which adds, removes or transforms parameters of a + callable. + + For example:: + + Callable[Concatenate[int, P], int] + + See PEP 612 for detailed information. + """ + __slots__ = () + + def __instancecheck__(self, obj): + raise TypeError("Concatenate cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError("Concatenate cannot be used with issubclass().") + + def __repr__(self): + return 'typing_extensions.Concatenate' + + def __getitem__(self, parameters): + return _concatenate_getitem(self, parameters) + + Concatenate = _ConcatenateAliasBase(_root=True) +# For 3.5.0 - 3.5.2 +else: + class _ConcatenateAliasMeta(typing.TypingMeta): + """Metaclass for Concatenate.""" + + def __instancecheck__(self, obj): + raise TypeError("TypeAlias cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError("TypeAlias cannot be used with issubclass().") + + def __call__(self, *args, **kwargs): + raise TypeError("Cannot instantiate TypeAlias") + + def __getitem__(self, parameters): + return _concatenate_getitem(self, parameters) + + class Concatenate(metaclass=_ConcatenateAliasMeta, _root=True): + """Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a + higher order function which adds, removes or transforms parameters of a + callable. + + For example:: + + Callable[Concatenate[int, P], int] + + See PEP 612 for detailed information. + """ + __slots__ = () + +if hasattr(typing, 'TypeGuard'): + TypeGuard = typing.TypeGuard +elif sys.version_info[:2] >= (3, 9): + class _TypeGuardForm(typing._SpecialForm, _root=True): + def __repr__(self): + return 'typing_extensions.' + self._name + + @_TypeGuardForm + def TypeGuard(self, parameters): + """Special typing form used to annotate the return type of a user-defined + type guard function. ``TypeGuard`` only accepts a single type argument. + At runtime, functions marked this way should return a boolean. + + ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static + type checkers to determine a more precise type of an expression within a + program's code flow. Usually type narrowing is done by analyzing + conditional code flow and applying the narrowing to a block of code. The + conditional expression here is sometimes referred to as a "type guard". + + Sometimes it would be convenient to use a user-defined boolean function + as a type guard. Such a function should use ``TypeGuard[...]`` as its + return type to alert static type checkers to this intention. + + Using ``-> TypeGuard`` tells the static type checker that for a given + function: + + 1. The return value is a boolean. + 2. If the return value is ``True``, the type of its argument + is the type inside ``TypeGuard``. + + For example:: + + def is_str(val: Union[str, float]): + # "isinstance" type guard + if isinstance(val, str): + # Type of ``val`` is narrowed to ``str`` + ... + else: + # Else, type of ``val`` is narrowed to ``float``. + ... + + Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower + form of ``TypeA`` (it can even be a wider form) and this may lead to + type-unsafe results. The main reason is to allow for things like + narrowing ``List[object]`` to ``List[str]`` even though the latter is not + a subtype of the former, since ``List`` is invariant. The responsibility of + writing type-safe type guards is left to the user. + + ``TypeGuard`` also works with type variables. For more information, see + PEP 647 (User-Defined Type Guards). + """ + item = typing._type_check(parameters, '{} accepts only single type.'.format(self)) + return _GenericAlias(self, (item,)) + +elif sys.version_info[:2] >= (3, 7): + class _TypeGuardForm(typing._SpecialForm, _root=True): + + def __repr__(self): + return 'typing_extensions.' + self._name + + def __getitem__(self, parameters): + item = typing._type_check(parameters, + '{} accepts only a single type'.format(self._name)) + return _GenericAlias(self, (item,)) + + TypeGuard = _TypeGuardForm( + 'TypeGuard', + doc="""Special typing form used to annotate the return type of a user-defined + type guard function. ``TypeGuard`` only accepts a single type argument. + At runtime, functions marked this way should return a boolean. + + ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static + type checkers to determine a more precise type of an expression within a + program's code flow. Usually type narrowing is done by analyzing + conditional code flow and applying the narrowing to a block of code. The + conditional expression here is sometimes referred to as a "type guard". + + Sometimes it would be convenient to use a user-defined boolean function + as a type guard. Such a function should use ``TypeGuard[...]`` as its + return type to alert static type checkers to this intention. + + Using ``-> TypeGuard`` tells the static type checker that for a given + function: + + 1. The return value is a boolean. + 2. If the return value is ``True``, the type of its argument + is the type inside ``TypeGuard``. + + For example:: + + def is_str(val: Union[str, float]): + # "isinstance" type guard + if isinstance(val, str): + # Type of ``val`` is narrowed to ``str`` + ... + else: + # Else, type of ``val`` is narrowed to ``float``. + ... + + Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower + form of ``TypeA`` (it can even be a wider form) and this may lead to + type-unsafe results. The main reason is to allow for things like + narrowing ``List[object]`` to ``List[str]`` even though the latter is not + a subtype of the former, since ``List`` is invariant. The responsibility of + writing type-safe type guards is left to the user. + + ``TypeGuard`` also works with type variables. For more information, see + PEP 647 (User-Defined Type Guards). + """) +elif hasattr(typing, '_FinalTypingBase'): + class _TypeGuard(typing._FinalTypingBase, _root=True): + """Special typing form used to annotate the return type of a user-defined + type guard function. ``TypeGuard`` only accepts a single type argument. + At runtime, functions marked this way should return a boolean. + + ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static + type checkers to determine a more precise type of an expression within a + program's code flow. Usually type narrowing is done by analyzing + conditional code flow and applying the narrowing to a block of code. The + conditional expression here is sometimes referred to as a "type guard". + + Sometimes it would be convenient to use a user-defined boolean function + as a type guard. Such a function should use ``TypeGuard[...]`` as its + return type to alert static type checkers to this intention. + + Using ``-> TypeGuard`` tells the static type checker that for a given + function: + + 1. The return value is a boolean. + 2. If the return value is ``True``, the type of its argument + is the type inside ``TypeGuard``. + + For example:: + + def is_str(val: Union[str, float]): + # "isinstance" type guard + if isinstance(val, str): + # Type of ``val`` is narrowed to ``str`` + ... + else: + # Else, type of ``val`` is narrowed to ``float``. + ... + + Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower + form of ``TypeA`` (it can even be a wider form) and this may lead to + type-unsafe results. The main reason is to allow for things like + narrowing ``List[object]`` to ``List[str]`` even though the latter is not + a subtype of the former, since ``List`` is invariant. The responsibility of + writing type-safe type guards is left to the user. + + ``TypeGuard`` also works with type variables. For more information, see + PEP 647 (User-Defined Type Guards). + """ + + __slots__ = ('__type__',) + + def __init__(self, tp=None, **kwds): + self.__type__ = tp + + def __getitem__(self, item): + cls = type(self) + if self.__type__ is None: + return cls(typing._type_check(item, + '{} accepts only a single type.'.format(cls.__name__[1:])), + _root=True) + raise TypeError('{} cannot be further subscripted' + .format(cls.__name__[1:])) + + def _eval_type(self, globalns, localns): + new_tp = typing._eval_type(self.__type__, globalns, localns) + if new_tp == self.__type__: + return self + return type(self)(new_tp, _root=True) + + def __repr__(self): + r = super().__repr__() + if self.__type__ is not None: + r += '[{}]'.format(typing._type_repr(self.__type__)) + return r + + def __hash__(self): + return hash((type(self).__name__, self.__type__)) + + def __eq__(self, other): + if not isinstance(other, _TypeGuard): + return NotImplemented + if self.__type__ is not None: + return self.__type__ == other.__type__ + return self is other + + TypeGuard = _TypeGuard(_root=True) +else: + class _TypeGuardMeta(typing.TypingMeta): + """Metaclass for TypeGuard""" + + def __new__(cls, name, bases, namespace, tp=None, _root=False): + self = super().__new__(cls, name, bases, namespace, _root=_root) + if tp is not None: + self.__type__ = tp + return self + + def __instancecheck__(self, obj): + raise TypeError("TypeGuard cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError("TypeGuard cannot be used with issubclass().") + + def __getitem__(self, item): + cls = type(self) + if self.__type__ is not None: + raise TypeError('{} cannot be further subscripted' + .format(cls.__name__[1:])) + + param = typing._type_check( + item, + '{} accepts only single type.'.format(cls.__name__[1:])) + return cls(self.__name__, self.__bases__, + dict(self.__dict__), tp=param, _root=True) + + def _eval_type(self, globalns, localns): + new_tp = typing._eval_type(self.__type__, globalns, localns) + if new_tp == self.__type__: + return self + return type(self)(self.__name__, self.__bases__, + dict(self.__dict__), tp=self.__type__, + _root=True) + + def __repr__(self): + r = super().__repr__() + if self.__type__ is not None: + r += '[{}]'.format(typing._type_repr(self.__type__)) + return r + + def __hash__(self): + return hash((type(self).__name__, self.__type__)) + + def __eq__(self, other): + if not hasattr(other, "__type__"): + return NotImplemented + if self.__type__ is not None: + return self.__type__ == other.__type__ + return self is other + + class TypeGuard(typing.Final, metaclass=_TypeGuardMeta, _root=True): + """Special typing form used to annotate the return type of a user-defined + type guard function. ``TypeGuard`` only accepts a single type argument. + At runtime, functions marked this way should return a boolean. + + ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static + type checkers to determine a more precise type of an expression within a + program's code flow. Usually type narrowing is done by analyzing + conditional code flow and applying the narrowing to a block of code. The + conditional expression here is sometimes referred to as a "type guard". + + Sometimes it would be convenient to use a user-defined boolean function + as a type guard. Such a function should use ``TypeGuard[...]`` as its + return type to alert static type checkers to this intention. + + Using ``-> TypeGuard`` tells the static type checker that for a given + function: + + 1. The return value is a boolean. + 2. If the return value is ``True``, the type of its argument + is the type inside ``TypeGuard``. + + For example:: + + def is_str(val: Union[str, float]): + # "isinstance" type guard + if isinstance(val, str): + # Type of ``val`` is narrowed to ``str`` + ... + else: + # Else, type of ``val`` is narrowed to ``float``. + ... + + Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower + form of ``TypeA`` (it can even be a wider form) and this may lead to + type-unsafe results. The main reason is to allow for things like + narrowing ``List[object]`` to ``List[str]`` even though the latter is not + a subtype of the former, since ``List`` is invariant. The responsibility of + writing type-safe type guards is left to the user. + + ``TypeGuard`` also works with type variables. For more information, see + PEP 647 (User-Defined Type Guards). + """ + __type__ = None diff --git a/dist/ba_data/python-site-packages/yaml/__init__.py b/dist/ba_data/python-site-packages/yaml/__init__.py index 13d687c..465041d 100644 --- a/dist/ba_data/python-site-packages/yaml/__init__.py +++ b/dist/ba_data/python-site-packages/yaml/__init__.py @@ -8,7 +8,7 @@ from .nodes import * from .loader import * from .dumper import * -__version__ = '5.3.1' +__version__ = '6.0' try: from .cyaml import * __with_libyaml__ = True @@ -18,41 +18,12 @@ except ImportError: import io #------------------------------------------------------------------------------ -# Warnings control +# XXX "Warnings control" is now deprecated. Leaving in the API function to not +# break code that uses it. #------------------------------------------------------------------------------ - -# 'Global' warnings state: -_warnings_enabled = { - 'YAMLLoadWarning': True, -} - -# Get or set global warnings' state def warnings(settings=None): if settings is None: - return _warnings_enabled - - if type(settings) is dict: - for key in settings: - if key in _warnings_enabled: - _warnings_enabled[key] = settings[key] - -# Warn when load() is called without Loader=... -class YAMLLoadWarning(RuntimeWarning): - pass - -def load_warning(method): - if _warnings_enabled['YAMLLoadWarning'] is False: - return - - import warnings - - message = ( - "calling yaml.%s() without Loader=... is deprecated, as the " - "default Loader is unsafe. Please read " - "https://msg.pyyaml.org/load for full details." - ) % method - - warnings.warn(message, YAMLLoadWarning, stacklevel=3) + return {} #------------------------------------------------------------------------------ def scan(stream, Loader=Loader): @@ -100,30 +71,22 @@ def compose_all(stream, Loader=Loader): finally: loader.dispose() -def load(stream, Loader=None): +def load(stream, Loader): """ Parse the first YAML document in a stream and produce the corresponding Python object. """ - if Loader is None: - load_warning('load') - Loader = FullLoader - loader = Loader(stream) try: return loader.get_single_data() finally: loader.dispose() -def load_all(stream, Loader=None): +def load_all(stream, Loader): """ Parse all YAML documents in a stream and produce corresponding Python objects. """ - if Loader is None: - load_warning('load_all') - Loader = FullLoader - loader = Loader(stream) try: while loader.check_data(): diff --git a/dist/ba_data/python-site-packages/yaml/__pycache__/__init__.cpython-38.opt-1.pyc b/dist/ba_data/python-site-packages/yaml/__pycache__/__init__.cpython-38.opt-1.pyc index 4104370..b8a764f 100644 Binary files a/dist/ba_data/python-site-packages/yaml/__pycache__/__init__.cpython-38.opt-1.pyc and b/dist/ba_data/python-site-packages/yaml/__pycache__/__init__.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python-site-packages/yaml/__pycache__/__init__.cpython-39.opt-1.pyc b/dist/ba_data/python-site-packages/yaml/__pycache__/__init__.cpython-39.opt-1.pyc new file mode 100644 index 0000000..257f543 Binary files /dev/null and b/dist/ba_data/python-site-packages/yaml/__pycache__/__init__.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python-site-packages/yaml/__pycache__/composer.cpython-39.opt-1.pyc b/dist/ba_data/python-site-packages/yaml/__pycache__/composer.cpython-39.opt-1.pyc new file mode 100644 index 0000000..e25a899 Binary files /dev/null and b/dist/ba_data/python-site-packages/yaml/__pycache__/composer.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python-site-packages/yaml/__pycache__/constructor.cpython-38.opt-1.pyc b/dist/ba_data/python-site-packages/yaml/__pycache__/constructor.cpython-38.opt-1.pyc index 5c6b841..6542a83 100644 Binary files a/dist/ba_data/python-site-packages/yaml/__pycache__/constructor.cpython-38.opt-1.pyc and b/dist/ba_data/python-site-packages/yaml/__pycache__/constructor.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python-site-packages/yaml/__pycache__/constructor.cpython-39.opt-1.pyc b/dist/ba_data/python-site-packages/yaml/__pycache__/constructor.cpython-39.opt-1.pyc new file mode 100644 index 0000000..afb5399 Binary files /dev/null and b/dist/ba_data/python-site-packages/yaml/__pycache__/constructor.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python-site-packages/yaml/__pycache__/cyaml.cpython-38.opt-1.pyc b/dist/ba_data/python-site-packages/yaml/__pycache__/cyaml.cpython-38.opt-1.pyc index e5764a2..238e29d 100644 Binary files a/dist/ba_data/python-site-packages/yaml/__pycache__/cyaml.cpython-38.opt-1.pyc and b/dist/ba_data/python-site-packages/yaml/__pycache__/cyaml.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python-site-packages/yaml/__pycache__/cyaml.cpython-39.opt-1.pyc b/dist/ba_data/python-site-packages/yaml/__pycache__/cyaml.cpython-39.opt-1.pyc new file mode 100644 index 0000000..1d2e8d8 Binary files /dev/null and b/dist/ba_data/python-site-packages/yaml/__pycache__/cyaml.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python-site-packages/yaml/__pycache__/dumper.cpython-39.opt-1.pyc b/dist/ba_data/python-site-packages/yaml/__pycache__/dumper.cpython-39.opt-1.pyc new file mode 100644 index 0000000..83a3031 Binary files /dev/null and b/dist/ba_data/python-site-packages/yaml/__pycache__/dumper.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python-site-packages/yaml/__pycache__/emitter.cpython-39.opt-1.pyc b/dist/ba_data/python-site-packages/yaml/__pycache__/emitter.cpython-39.opt-1.pyc new file mode 100644 index 0000000..bbb0b9f Binary files /dev/null and b/dist/ba_data/python-site-packages/yaml/__pycache__/emitter.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python-site-packages/yaml/__pycache__/error.cpython-39.opt-1.pyc b/dist/ba_data/python-site-packages/yaml/__pycache__/error.cpython-39.opt-1.pyc new file mode 100644 index 0000000..e90dea0 Binary files /dev/null and b/dist/ba_data/python-site-packages/yaml/__pycache__/error.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python-site-packages/yaml/__pycache__/events.cpython-39.opt-1.pyc b/dist/ba_data/python-site-packages/yaml/__pycache__/events.cpython-39.opt-1.pyc new file mode 100644 index 0000000..65a5459 Binary files /dev/null and b/dist/ba_data/python-site-packages/yaml/__pycache__/events.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python-site-packages/yaml/__pycache__/loader.cpython-39.opt-1.pyc b/dist/ba_data/python-site-packages/yaml/__pycache__/loader.cpython-39.opt-1.pyc new file mode 100644 index 0000000..75d057a Binary files /dev/null and b/dist/ba_data/python-site-packages/yaml/__pycache__/loader.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python-site-packages/yaml/__pycache__/nodes.cpython-39.opt-1.pyc b/dist/ba_data/python-site-packages/yaml/__pycache__/nodes.cpython-39.opt-1.pyc new file mode 100644 index 0000000..713192b Binary files /dev/null and b/dist/ba_data/python-site-packages/yaml/__pycache__/nodes.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python-site-packages/yaml/__pycache__/parser.cpython-39.opt-1.pyc b/dist/ba_data/python-site-packages/yaml/__pycache__/parser.cpython-39.opt-1.pyc new file mode 100644 index 0000000..59af3d8 Binary files /dev/null and b/dist/ba_data/python-site-packages/yaml/__pycache__/parser.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python-site-packages/yaml/__pycache__/reader.cpython-39.opt-1.pyc b/dist/ba_data/python-site-packages/yaml/__pycache__/reader.cpython-39.opt-1.pyc new file mode 100644 index 0000000..343be92 Binary files /dev/null and b/dist/ba_data/python-site-packages/yaml/__pycache__/reader.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python-site-packages/yaml/__pycache__/representer.cpython-38.opt-1.pyc b/dist/ba_data/python-site-packages/yaml/__pycache__/representer.cpython-38.opt-1.pyc index 7def593..ec862c3 100644 Binary files a/dist/ba_data/python-site-packages/yaml/__pycache__/representer.cpython-38.opt-1.pyc and b/dist/ba_data/python-site-packages/yaml/__pycache__/representer.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python-site-packages/yaml/__pycache__/representer.cpython-39.opt-1.pyc b/dist/ba_data/python-site-packages/yaml/__pycache__/representer.cpython-39.opt-1.pyc new file mode 100644 index 0000000..6607cda Binary files /dev/null and b/dist/ba_data/python-site-packages/yaml/__pycache__/representer.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python-site-packages/yaml/__pycache__/resolver.cpython-38.opt-1.pyc b/dist/ba_data/python-site-packages/yaml/__pycache__/resolver.cpython-38.opt-1.pyc index 338a50a..60327a5 100644 Binary files a/dist/ba_data/python-site-packages/yaml/__pycache__/resolver.cpython-38.opt-1.pyc and b/dist/ba_data/python-site-packages/yaml/__pycache__/resolver.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python-site-packages/yaml/__pycache__/resolver.cpython-39.opt-1.pyc b/dist/ba_data/python-site-packages/yaml/__pycache__/resolver.cpython-39.opt-1.pyc new file mode 100644 index 0000000..eb6c8a9 Binary files /dev/null and b/dist/ba_data/python-site-packages/yaml/__pycache__/resolver.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python-site-packages/yaml/__pycache__/scanner.cpython-39.opt-1.pyc b/dist/ba_data/python-site-packages/yaml/__pycache__/scanner.cpython-39.opt-1.pyc new file mode 100644 index 0000000..efe6870 Binary files /dev/null and b/dist/ba_data/python-site-packages/yaml/__pycache__/scanner.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python-site-packages/yaml/__pycache__/serializer.cpython-39.opt-1.pyc b/dist/ba_data/python-site-packages/yaml/__pycache__/serializer.cpython-39.opt-1.pyc new file mode 100644 index 0000000..780a5bf Binary files /dev/null and b/dist/ba_data/python-site-packages/yaml/__pycache__/serializer.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python-site-packages/yaml/__pycache__/tokens.cpython-39.opt-1.pyc b/dist/ba_data/python-site-packages/yaml/__pycache__/tokens.cpython-39.opt-1.pyc new file mode 100644 index 0000000..cfddd93 Binary files /dev/null and b/dist/ba_data/python-site-packages/yaml/__pycache__/tokens.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python-site-packages/yaml/constructor.py b/dist/ba_data/python-site-packages/yaml/constructor.py index 1948b12..619acd3 100644 --- a/dist/ba_data/python-site-packages/yaml/constructor.py +++ b/dist/ba_data/python-site-packages/yaml/constructor.py @@ -710,18 +710,6 @@ FullConstructor.add_multi_constructor( 'tag:yaml.org,2002:python/name:', FullConstructor.construct_python_name) -FullConstructor.add_multi_constructor( - 'tag:yaml.org,2002:python/module:', - FullConstructor.construct_python_module) - -FullConstructor.add_multi_constructor( - 'tag:yaml.org,2002:python/object:', - FullConstructor.construct_python_object) - -FullConstructor.add_multi_constructor( - 'tag:yaml.org,2002:python/object/new:', - FullConstructor.construct_python_object_new) - class UnsafeConstructor(FullConstructor): def find_python_module(self, name, mark): @@ -738,6 +726,18 @@ class UnsafeConstructor(FullConstructor): return super(UnsafeConstructor, self).set_python_instance_state( instance, state, unsafe=True) +UnsafeConstructor.add_multi_constructor( + 'tag:yaml.org,2002:python/module:', + UnsafeConstructor.construct_python_module) + +UnsafeConstructor.add_multi_constructor( + 'tag:yaml.org,2002:python/object:', + UnsafeConstructor.construct_python_object) + +UnsafeConstructor.add_multi_constructor( + 'tag:yaml.org,2002:python/object/new:', + UnsafeConstructor.construct_python_object_new) + UnsafeConstructor.add_multi_constructor( 'tag:yaml.org,2002:python/object/apply:', UnsafeConstructor.construct_python_object_apply) diff --git a/dist/ba_data/python-site-packages/yaml/cyaml.py b/dist/ba_data/python-site-packages/yaml/cyaml.py index 1e606c7..0c21345 100644 --- a/dist/ba_data/python-site-packages/yaml/cyaml.py +++ b/dist/ba_data/python-site-packages/yaml/cyaml.py @@ -4,7 +4,7 @@ __all__ = [ 'CBaseDumper', 'CSafeDumper', 'CDumper' ] -from _yaml import CParser, CEmitter +from yaml._yaml import CParser, CEmitter from .constructor import * diff --git a/dist/ba_data/python-site-packages/yaml/representer.py b/dist/ba_data/python-site-packages/yaml/representer.py index 3b0b192..808ca06 100644 --- a/dist/ba_data/python-site-packages/yaml/representer.py +++ b/dist/ba_data/python-site-packages/yaml/representer.py @@ -369,7 +369,7 @@ Representer.add_representer(complex, Representer.add_representer(tuple, Representer.represent_tuple) -Representer.add_representer(type, +Representer.add_multi_representer(type, Representer.represent_name) Representer.add_representer(collections.OrderedDict, diff --git a/dist/ba_data/python-site-packages/yaml/resolver.py b/dist/ba_data/python-site-packages/yaml/resolver.py index 02b82e7..3522bda 100644 --- a/dist/ba_data/python-site-packages/yaml/resolver.py +++ b/dist/ba_data/python-site-packages/yaml/resolver.py @@ -146,8 +146,8 @@ class BaseResolver: resolvers = self.yaml_implicit_resolvers.get('', []) else: resolvers = self.yaml_implicit_resolvers.get(value[0], []) - resolvers += self.yaml_implicit_resolvers.get(None, []) - for tag, regexp in resolvers: + wildcard_resolvers = self.yaml_implicit_resolvers.get(None, []) + for tag, regexp in resolvers + wildcard_resolvers: if regexp.match(value): return tag implicit = implicit[1] @@ -177,7 +177,7 @@ Resolver.add_implicit_resolver( Resolver.add_implicit_resolver( 'tag:yaml.org,2002:float', re.compile(r'''^(?:[-+]?(?:[0-9][0-9_]*)\.[0-9_]*(?:[eE][-+][0-9]+)? - |\.[0-9_]+(?:[eE][-+][0-9]+)? + |\.[0-9][0-9_]*(?:[eE][-+][0-9]+)? |[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]* |[-+]?\.(?:inf|Inf|INF) |\.(?:nan|NaN|NAN))$''', re.X), diff --git a/dist/ba_data/python-site-packages/yaml/scanner.py b/dist/ba_data/python-site-packages/yaml/scanner.py index 7437ede..de925b0 100644 --- a/dist/ba_data/python-site-packages/yaml/scanner.py +++ b/dist/ba_data/python-site-packages/yaml/scanner.py @@ -1211,7 +1211,7 @@ class Scanner: for k in range(length): if self.peek(k) not in '0123456789ABCDEFabcdef': raise ScannerError("while scanning a double-quoted scalar", start_mark, - "expected escape sequence of %d hexdecimal numbers, but found %r" % + "expected escape sequence of %d hexadecimal numbers, but found %r" % (length, self.peek(k)), self.get_mark()) code = int(self.prefix(length), 16) chunks.append(chr(code)) @@ -1403,7 +1403,7 @@ class Scanner: for k in range(2): if self.peek(k) not in '0123456789ABCDEFabcdef': raise ScannerError("while scanning a %s" % name, start_mark, - "expected URI escape sequence of 2 hexdecimal numbers, but found %r" + "expected URI escape sequence of 2 hexadecimal numbers, but found %r" % self.peek(k), self.get_mark()) codes.append(int(self.prefix(2), 16)) self.forward(2) diff --git a/dist/ba_data/python/ba/__init__.py b/dist/ba_data/python/ba/__init__.py index 72792c7..24c1cee 100644 --- a/dist/ba_data/python/ba/__init__.py +++ b/dist/ba_data/python/ba/__init__.py @@ -29,8 +29,8 @@ from ba._coopgame import CoopGameActivity from ba._coopsession import CoopSession from ba._dependency import (Dependency, DependencyComponent, DependencySet, AssetPackage) -from ba._enums import (TimeType, Permission, TimeFormat, SpecialChar, - InputType, UIScale) +from ba._generated.enums import (TimeType, Permission, TimeFormat, SpecialChar, + InputType, UIScale) from ba._error import ( print_exception, print_error, ContextError, NotFoundError, PlayerNotFoundError, SessionPlayerNotFoundError, NodeNotFoundError, diff --git a/dist/ba_data/python/ba/__pycache__/__init__.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/__init__.cpython-39.opt-1.pyc new file mode 100644 index 0000000..f6e02e5 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/__init__.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/__init__.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/__init__.cpython-39.pyc similarity index 76% rename from dist/ba_data/python/ba/__pycache__/__init__.cpython-38.opt-1.pyc rename to dist/ba_data/python/ba/__pycache__/__init__.cpython-39.pyc index 078a129..00b457b 100644 Binary files a/dist/ba_data/python/ba/__pycache__/__init__.cpython-38.opt-1.pyc and b/dist/ba_data/python/ba/__pycache__/__init__.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_account.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_account.cpython-38.opt-1.pyc deleted file mode 100644 index bf8db2d..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_account.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_account.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_account.cpython-39.opt-1.pyc new file mode 100644 index 0000000..74f3ed2 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_account.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_account.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_account.cpython-39.pyc new file mode 100644 index 0000000..cd2619a Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_account.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_achievement.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_achievement.cpython-38.opt-1.pyc deleted file mode 100644 index 7a06484..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_achievement.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_achievement.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_achievement.cpython-39.opt-1.pyc new file mode 100644 index 0000000..134f27d Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_achievement.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_achievement.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_achievement.cpython-39.pyc new file mode 100644 index 0000000..fe6150e Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_achievement.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_activity.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_activity.cpython-38.opt-1.pyc deleted file mode 100644 index 0f57dda..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_activity.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_activity.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_activity.cpython-39.opt-1.pyc new file mode 100644 index 0000000..7a18871 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_activity.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_activity.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_activity.cpython-39.pyc new file mode 100644 index 0000000..0fc328c Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_activity.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_activitytypes.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_activitytypes.cpython-38.opt-1.pyc deleted file mode 100644 index d1623af..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_activitytypes.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_activitytypes.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_activitytypes.cpython-39.opt-1.pyc new file mode 100644 index 0000000..1a0a1cd Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_activitytypes.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_activitytypes.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_activitytypes.cpython-39.pyc new file mode 100644 index 0000000..d611c0f Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_activitytypes.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_actor.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_actor.cpython-39.opt-1.pyc new file mode 100644 index 0000000..bdb0144 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_actor.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_actor.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_actor.cpython-39.pyc similarity index 79% rename from dist/ba_data/python/ba/__pycache__/_actor.cpython-38.opt-1.pyc rename to dist/ba_data/python/ba/__pycache__/_actor.cpython-39.pyc index 6422c5a..120d160 100644 Binary files a/dist/ba_data/python/ba/__pycache__/_actor.cpython-38.opt-1.pyc and b/dist/ba_data/python/ba/__pycache__/_actor.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_ads.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_ads.cpython-39.opt-1.pyc new file mode 100644 index 0000000..287a045 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_ads.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_ads.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_ads.cpython-39.pyc similarity index 74% rename from dist/ba_data/python/ba/__pycache__/_ads.cpython-38.opt-1.pyc rename to dist/ba_data/python/ba/__pycache__/_ads.cpython-39.pyc index 7a131ed..581d707 100644 Binary files a/dist/ba_data/python/ba/__pycache__/_ads.cpython-38.opt-1.pyc and b/dist/ba_data/python/ba/__pycache__/_ads.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_analytics.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_analytics.cpython-39.opt-1.pyc similarity index 71% rename from dist/ba_data/python/ba/__pycache__/_analytics.cpython-38.opt-1.pyc rename to dist/ba_data/python/ba/__pycache__/_analytics.cpython-39.opt-1.pyc index 60fc324..b2415af 100644 Binary files a/dist/ba_data/python/ba/__pycache__/_analytics.cpython-38.opt-1.pyc and b/dist/ba_data/python/ba/__pycache__/_analytics.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_analytics.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_analytics.cpython-39.pyc new file mode 100644 index 0000000..50e01d3 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_analytics.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_app.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_app.cpython-38.opt-1.pyc deleted file mode 100644 index 54105be..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_app.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_app.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_app.cpython-39.opt-1.pyc new file mode 100644 index 0000000..a76b74c Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_app.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_app.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_app.cpython-39.pyc new file mode 100644 index 0000000..3dce064 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_app.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_appconfig.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_appconfig.cpython-39.opt-1.pyc new file mode 100644 index 0000000..631acd8 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_appconfig.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_appconfig.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_appconfig.cpython-39.pyc similarity index 56% rename from dist/ba_data/python/ba/__pycache__/_appconfig.cpython-38.opt-1.pyc rename to dist/ba_data/python/ba/__pycache__/_appconfig.cpython-39.pyc index bed6d08..10f593d 100644 Binary files a/dist/ba_data/python/ba/__pycache__/_appconfig.cpython-38.opt-1.pyc and b/dist/ba_data/python/ba/__pycache__/_appconfig.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_appdelegate.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_appdelegate.cpython-38.opt-1.pyc deleted file mode 100644 index 8bf700d..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_appdelegate.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_appdelegate.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_appdelegate.cpython-39.opt-1.pyc new file mode 100644 index 0000000..f702511 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_appdelegate.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_appdelegate.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_appdelegate.cpython-39.pyc new file mode 100644 index 0000000..7a18ad4 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_appdelegate.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_appmode.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_appmode.cpython-39.opt-1.pyc new file mode 100644 index 0000000..312199a Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_appmode.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_apputils.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_apputils.cpython-38.opt-1.pyc deleted file mode 100644 index efda323..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_apputils.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_apputils.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_apputils.cpython-39.opt-1.pyc new file mode 100644 index 0000000..0143f03 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_apputils.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_apputils.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_apputils.cpython-39.pyc new file mode 100644 index 0000000..c89f071 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_apputils.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_assetmanager.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_assetmanager.cpython-39.opt-1.pyc new file mode 100644 index 0000000..d6994db Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_assetmanager.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_asyncio.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_asyncio.cpython-39.opt-1.pyc new file mode 100644 index 0000000..f710eee Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_asyncio.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_asyncio.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_asyncio.cpython-39.pyc new file mode 100644 index 0000000..63fe1e9 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_asyncio.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_benchmark.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_benchmark.cpython-38.opt-1.pyc deleted file mode 100644 index f990d30..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_benchmark.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_benchmark.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_benchmark.cpython-39.opt-1.pyc new file mode 100644 index 0000000..e5175e6 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_benchmark.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_benchmark.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_benchmark.cpython-39.pyc new file mode 100644 index 0000000..c6d340d Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_benchmark.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_campaign.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_campaign.cpython-39.opt-1.pyc new file mode 100644 index 0000000..f4cdb1d Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_campaign.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_campaign.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_campaign.cpython-39.pyc similarity index 55% rename from dist/ba_data/python/ba/__pycache__/_campaign.cpython-38.opt-1.pyc rename to dist/ba_data/python/ba/__pycache__/_campaign.cpython-39.pyc index edb989c..8f2ff4a 100644 Binary files a/dist/ba_data/python/ba/__pycache__/_campaign.cpython-38.opt-1.pyc and b/dist/ba_data/python/ba/__pycache__/_campaign.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_collision.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_collision.cpython-39.opt-1.pyc similarity index 60% rename from dist/ba_data/python/ba/__pycache__/_collision.cpython-38.opt-1.pyc rename to dist/ba_data/python/ba/__pycache__/_collision.cpython-39.opt-1.pyc index 768ce5f..e31ad3f 100644 Binary files a/dist/ba_data/python/ba/__pycache__/_collision.cpython-38.opt-1.pyc and b/dist/ba_data/python/ba/__pycache__/_collision.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_collision.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_collision.cpython-39.pyc new file mode 100644 index 0000000..2462229 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_collision.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_coopgame.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_coopgame.cpython-38.opt-1.pyc deleted file mode 100644 index 5201fa8..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_coopgame.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_coopgame.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_coopgame.cpython-39.opt-1.pyc new file mode 100644 index 0000000..7f026a7 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_coopgame.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_coopgame.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_coopgame.cpython-39.pyc new file mode 100644 index 0000000..c518ed5 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_coopgame.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_coopsession.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_coopsession.cpython-38.opt-1.pyc deleted file mode 100644 index 7a2c448..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_coopsession.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_coopsession.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_coopsession.cpython-39.opt-1.pyc new file mode 100644 index 0000000..5c4be50 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_coopsession.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_coopsession.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_coopsession.cpython-39.pyc new file mode 100644 index 0000000..beab326 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_coopsession.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_dependency.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_dependency.cpython-38.opt-1.pyc deleted file mode 100644 index 6c83466..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_dependency.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_dependency.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_dependency.cpython-39.opt-1.pyc new file mode 100644 index 0000000..2cc41a4 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_dependency.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_dependency.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_dependency.cpython-39.pyc new file mode 100644 index 0000000..6bb4e15 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_dependency.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_dualteamsession.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_dualteamsession.cpython-39.opt-1.pyc new file mode 100644 index 0000000..78fce61 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_dualteamsession.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_dualteamsession.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_dualteamsession.cpython-39.pyc similarity index 88% rename from dist/ba_data/python/ba/__pycache__/_dualteamsession.cpython-38.opt-1.pyc rename to dist/ba_data/python/ba/__pycache__/_dualteamsession.cpython-39.pyc index bcc1c28..f98c2be 100644 Binary files a/dist/ba_data/python/ba/__pycache__/_dualteamsession.cpython-38.opt-1.pyc and b/dist/ba_data/python/ba/__pycache__/_dualteamsession.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_error.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_error.cpython-38.opt-1.pyc deleted file mode 100644 index 5e9c42f..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_error.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_error.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_error.cpython-39.opt-1.pyc new file mode 100644 index 0000000..f61d2ef Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_error.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_error.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_error.cpython-39.pyc new file mode 100644 index 0000000..b8d04bd Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_error.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_freeforallsession.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_freeforallsession.cpython-38.opt-1.pyc deleted file mode 100644 index 37a94d8..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_freeforallsession.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_freeforallsession.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_freeforallsession.cpython-39.opt-1.pyc new file mode 100644 index 0000000..e7fda95 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_freeforallsession.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_freeforallsession.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_freeforallsession.cpython-39.pyc new file mode 100644 index 0000000..0265a5c Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_freeforallsession.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_gameactivity.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_gameactivity.cpython-38.opt-1.pyc deleted file mode 100644 index 5bb25a9..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_gameactivity.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_gameactivity.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_gameactivity.cpython-39.opt-1.pyc new file mode 100644 index 0000000..29d7645 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_gameactivity.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_gameactivity.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_gameactivity.cpython-39.pyc new file mode 100644 index 0000000..a3b4191 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_gameactivity.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_gameresults.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_gameresults.cpython-38.opt-1.pyc deleted file mode 100644 index 64ef779..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_gameresults.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_gameresults.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_gameresults.cpython-39.opt-1.pyc new file mode 100644 index 0000000..29142b9 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_gameresults.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_gameresults.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_gameresults.cpython-39.pyc new file mode 100644 index 0000000..384e46e Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_gameresults.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_gameutils.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_gameutils.cpython-38.opt-1.pyc deleted file mode 100644 index 9256197..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_gameutils.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_gameutils.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_gameutils.cpython-39.opt-1.pyc new file mode 100644 index 0000000..394cc29 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_gameutils.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_gameutils.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_gameutils.cpython-39.pyc new file mode 100644 index 0000000..a6273c9 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_gameutils.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_general.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_general.cpython-39.opt-1.pyc similarity index 51% rename from dist/ba_data/python/ba/__pycache__/_general.cpython-38.opt-1.pyc rename to dist/ba_data/python/ba/__pycache__/_general.cpython-39.opt-1.pyc index ebde2bc..ce34eca 100644 Binary files a/dist/ba_data/python/ba/__pycache__/_general.cpython-38.opt-1.pyc and b/dist/ba_data/python/ba/__pycache__/_general.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_general.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_general.cpython-39.pyc new file mode 100644 index 0000000..1bef2a4 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_general.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_hooks.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_hooks.cpython-38.opt-1.pyc deleted file mode 100644 index 3428410..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_hooks.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_hooks.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_hooks.cpython-39.opt-1.pyc new file mode 100644 index 0000000..b33b17e Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_hooks.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_hooks.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_hooks.cpython-39.pyc new file mode 100644 index 0000000..76018c6 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_hooks.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_input.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_input.cpython-38.opt-1.pyc deleted file mode 100644 index 46b08d9..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_input.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_input.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_input.cpython-39.opt-1.pyc new file mode 100644 index 0000000..84f9c52 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_input.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_input.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_input.cpython-39.pyc new file mode 100644 index 0000000..3021eec Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_input.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_keyboard.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_keyboard.cpython-38.opt-1.pyc deleted file mode 100644 index cd52c32..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_keyboard.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_keyboard.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_keyboard.cpython-39.opt-1.pyc new file mode 100644 index 0000000..326a2d5 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_keyboard.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_keyboard.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_keyboard.cpython-39.pyc new file mode 100644 index 0000000..38ca351 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_keyboard.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_language.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_language.cpython-38.opt-1.pyc deleted file mode 100644 index ad41c93..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_language.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_language.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_language.cpython-39.opt-1.pyc new file mode 100644 index 0000000..1e75f43 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_language.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_language.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_language.cpython-39.pyc new file mode 100644 index 0000000..7b8b4e5 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_language.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_level.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_level.cpython-38.opt-1.pyc deleted file mode 100644 index 589bbbc..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_level.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_level.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_level.cpython-39.opt-1.pyc new file mode 100644 index 0000000..211fcde Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_level.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_level.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_level.cpython-39.pyc new file mode 100644 index 0000000..c3b055f Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_level.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_lobby.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_lobby.cpython-38.opt-1.pyc deleted file mode 100644 index 9765d9c..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_lobby.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_lobby.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_lobby.cpython-39.opt-1.pyc new file mode 100644 index 0000000..1fd01ab Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_lobby.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_lobby.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_lobby.cpython-39.pyc new file mode 100644 index 0000000..4a2d83a Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_lobby.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_map.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_map.cpython-38.opt-1.pyc deleted file mode 100644 index 7c27d09..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_map.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_map.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_map.cpython-39.opt-1.pyc new file mode 100644 index 0000000..6d87737 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_map.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_map.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_map.cpython-39.pyc new file mode 100644 index 0000000..7a0a4e1 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_map.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_math.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_math.cpython-38.opt-1.pyc deleted file mode 100644 index 492177a..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_math.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_math.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_math.cpython-39.opt-1.pyc new file mode 100644 index 0000000..0fcfbcb Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_math.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_math.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_math.cpython-39.pyc new file mode 100644 index 0000000..051e9b8 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_math.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_messages.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_messages.cpython-38.opt-1.pyc deleted file mode 100644 index bf0d185..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_messages.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_messages.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_messages.cpython-39.opt-1.pyc new file mode 100644 index 0000000..f3bd688 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_messages.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_messages.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_messages.cpython-39.pyc new file mode 100644 index 0000000..3c639af Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_messages.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_meta.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_meta.cpython-38.opt-1.pyc deleted file mode 100644 index 034d7d8..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_meta.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_meta.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_meta.cpython-39.opt-1.pyc new file mode 100644 index 0000000..a77fa53 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_meta.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_meta.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_meta.cpython-39.pyc new file mode 100644 index 0000000..e6e9fbc Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_meta.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_multiteamsession.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_multiteamsession.cpython-38.opt-1.pyc deleted file mode 100644 index c2f2014..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_multiteamsession.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_multiteamsession.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_multiteamsession.cpython-39.opt-1.pyc new file mode 100644 index 0000000..14aa66a Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_multiteamsession.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_multiteamsession.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_multiteamsession.cpython-39.pyc new file mode 100644 index 0000000..14df42e Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_multiteamsession.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_music.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_music.cpython-38.opt-1.pyc deleted file mode 100644 index 4d0bd93..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_music.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_music.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_music.cpython-39.opt-1.pyc new file mode 100644 index 0000000..3e077ed Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_music.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_music.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_music.cpython-39.pyc new file mode 100644 index 0000000..c9961a1 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_music.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_net.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_net.cpython-38.opt-1.pyc deleted file mode 100644 index 52f02ca..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_net.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_net.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_net.cpython-39.opt-1.pyc new file mode 100644 index 0000000..c4721af Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_net.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_net.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_net.cpython-39.pyc new file mode 100644 index 0000000..528aed8 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_net.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_nodeactor.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_nodeactor.cpython-39.opt-1.pyc similarity index 82% rename from dist/ba_data/python/ba/__pycache__/_nodeactor.cpython-38.opt-1.pyc rename to dist/ba_data/python/ba/__pycache__/_nodeactor.cpython-39.opt-1.pyc index d9bedb5..00e628b 100644 Binary files a/dist/ba_data/python/ba/__pycache__/_nodeactor.cpython-38.opt-1.pyc and b/dist/ba_data/python/ba/__pycache__/_nodeactor.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_nodeactor.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_nodeactor.cpython-39.pyc new file mode 100644 index 0000000..167ef54 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_nodeactor.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_player.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_player.cpython-39.opt-1.pyc similarity index 57% rename from dist/ba_data/python/ba/__pycache__/_player.cpython-38.opt-1.pyc rename to dist/ba_data/python/ba/__pycache__/_player.cpython-39.opt-1.pyc index 098eabc..c3dc606 100644 Binary files a/dist/ba_data/python/ba/__pycache__/_player.cpython-38.opt-1.pyc and b/dist/ba_data/python/ba/__pycache__/_player.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_player.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_player.cpython-39.pyc new file mode 100644 index 0000000..bfcc731 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_player.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_playlist.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_playlist.cpython-39.opt-1.pyc new file mode 100644 index 0000000..119000c Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_playlist.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_playlist.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_playlist.cpython-39.pyc similarity index 53% rename from dist/ba_data/python/ba/__pycache__/_playlist.cpython-38.opt-1.pyc rename to dist/ba_data/python/ba/__pycache__/_playlist.cpython-39.pyc index 7a43b3f..f8b1974 100644 Binary files a/dist/ba_data/python/ba/__pycache__/_playlist.cpython-38.opt-1.pyc and b/dist/ba_data/python/ba/__pycache__/_playlist.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_plugin.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_plugin.cpython-38.opt-1.pyc deleted file mode 100644 index da2b886..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_plugin.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_plugin.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_plugin.cpython-39.opt-1.pyc new file mode 100644 index 0000000..382aa8c Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_plugin.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_plugin.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_plugin.cpython-39.pyc new file mode 100644 index 0000000..1f57219 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_plugin.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_powerup.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_powerup.cpython-38.opt-1.pyc deleted file mode 100644 index 9c1dc2f..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_powerup.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_powerup.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_powerup.cpython-39.opt-1.pyc new file mode 100644 index 0000000..fea7ef6 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_powerup.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_powerup.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_powerup.cpython-39.pyc new file mode 100644 index 0000000..3509091 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_powerup.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_profile.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_profile.cpython-38.opt-1.pyc deleted file mode 100644 index 17ea9c6..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_profile.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_profile.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_profile.cpython-39.opt-1.pyc new file mode 100644 index 0000000..877acdb Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_profile.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_profile.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_profile.cpython-39.pyc new file mode 100644 index 0000000..dfbfffe Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_profile.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_score.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_score.cpython-39.opt-1.pyc similarity index 70% rename from dist/ba_data/python/ba/__pycache__/_score.cpython-38.opt-1.pyc rename to dist/ba_data/python/ba/__pycache__/_score.cpython-39.opt-1.pyc index e2f1a2b..d60c502 100644 Binary files a/dist/ba_data/python/ba/__pycache__/_score.cpython-38.opt-1.pyc and b/dist/ba_data/python/ba/__pycache__/_score.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_score.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_score.cpython-39.pyc new file mode 100644 index 0000000..1cf2486 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_score.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_servermode.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_servermode.cpython-38.opt-1.pyc deleted file mode 100644 index 1bec71a..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_servermode.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_servermode.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_servermode.cpython-39.opt-1.pyc new file mode 100644 index 0000000..def3c32 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_servermode.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_servermode.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_servermode.cpython-39.pyc new file mode 100644 index 0000000..c19e988 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_servermode.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_session.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_session.cpython-38.opt-1.pyc deleted file mode 100644 index 622a2c2..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_session.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_session.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_session.cpython-39.opt-1.pyc new file mode 100644 index 0000000..d5a997d Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_session.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_session.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_session.cpython-39.pyc new file mode 100644 index 0000000..cf06716 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_session.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_settings.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_settings.cpython-38.opt-1.pyc deleted file mode 100644 index 5eebb39..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_settings.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_settings.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_settings.cpython-39.opt-1.pyc new file mode 100644 index 0000000..cec7030 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_settings.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_settings.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_settings.cpython-39.pyc new file mode 100644 index 0000000..fd49b31 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_settings.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_stats.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_stats.cpython-38.opt-1.pyc deleted file mode 100644 index 060e00b..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_stats.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_stats.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_stats.cpython-39.opt-1.pyc new file mode 100644 index 0000000..0682cff Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_stats.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_stats.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_stats.cpython-39.pyc new file mode 100644 index 0000000..865b3e3 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_stats.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_store.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_store.cpython-38.opt-1.pyc deleted file mode 100644 index 07bc435..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_store.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_store.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_store.cpython-39.opt-1.pyc new file mode 100644 index 0000000..fe04b00 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_store.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_store.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_store.cpython-39.pyc new file mode 100644 index 0000000..6056930 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_store.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_team.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_team.cpython-39.opt-1.pyc similarity index 54% rename from dist/ba_data/python/ba/__pycache__/_team.cpython-38.opt-1.pyc rename to dist/ba_data/python/ba/__pycache__/_team.cpython-39.opt-1.pyc index 2f0201d..4512e66 100644 Binary files a/dist/ba_data/python/ba/__pycache__/_team.cpython-38.opt-1.pyc and b/dist/ba_data/python/ba/__pycache__/_team.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_team.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_team.cpython-39.pyc new file mode 100644 index 0000000..eb05c9d Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_team.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_teamgame.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_teamgame.cpython-38.opt-1.pyc deleted file mode 100644 index c9ac685..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_teamgame.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_teamgame.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_teamgame.cpython-39.opt-1.pyc new file mode 100644 index 0000000..2439698 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_teamgame.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_teamgame.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_teamgame.cpython-39.pyc new file mode 100644 index 0000000..cc7c92f Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_teamgame.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_tips.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_tips.cpython-39.opt-1.pyc similarity index 69% rename from dist/ba_data/python/ba/__pycache__/_tips.cpython-38.opt-1.pyc rename to dist/ba_data/python/ba/__pycache__/_tips.cpython-39.opt-1.pyc index 08c409d..dd5d96f 100644 Binary files a/dist/ba_data/python/ba/__pycache__/_tips.cpython-38.opt-1.pyc and b/dist/ba_data/python/ba/__pycache__/_tips.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_tips.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_tips.cpython-39.pyc new file mode 100644 index 0000000..95adac8 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_tips.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_tournament.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_tournament.cpython-38.opt-1.pyc deleted file mode 100644 index 4077a74..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_tournament.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_tournament.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_tournament.cpython-39.opt-1.pyc new file mode 100644 index 0000000..9ee9c39 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_tournament.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_tournament.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_tournament.cpython-39.pyc new file mode 100644 index 0000000..a0ca22a Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_tournament.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_ui.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_ui.cpython-38.opt-1.pyc deleted file mode 100644 index 98547d8..0000000 Binary files a/dist/ba_data/python/ba/__pycache__/_ui.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/ba/__pycache__/_ui.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/_ui.cpython-39.opt-1.pyc new file mode 100644 index 0000000..14942b2 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_ui.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_ui.cpython-39.pyc b/dist/ba_data/python/ba/__pycache__/_ui.cpython-39.pyc new file mode 100644 index 0000000..43f4193 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/_ui.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/deprecated.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/deprecated.cpython-39.opt-1.pyc new file mode 100644 index 0000000..495f51e Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/deprecated.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/internal.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/internal.cpython-39.opt-1.pyc new file mode 100644 index 0000000..140f9bb Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/internal.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/internal.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/internal.cpython-39.pyc similarity index 87% rename from dist/ba_data/python/ba/__pycache__/internal.cpython-38.opt-1.pyc rename to dist/ba_data/python/ba/__pycache__/internal.cpython-39.pyc index 273cc72..d9a4cc5 100644 Binary files a/dist/ba_data/python/ba/__pycache__/internal.cpython-38.opt-1.pyc and b/dist/ba_data/python/ba/__pycache__/internal.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/macmusicapp.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/macmusicapp.cpython-39.opt-1.pyc new file mode 100644 index 0000000..7715f21 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/macmusicapp.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/modutils.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/modutils.cpython-39.opt-1.pyc new file mode 100644 index 0000000..6e62df9 Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/modutils.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/osmusic.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/__pycache__/osmusic.cpython-39.opt-1.pyc new file mode 100644 index 0000000..5b3289c Binary files /dev/null and b/dist/ba_data/python/ba/__pycache__/osmusic.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/_account.py b/dist/ba_data/python/ba/_account.py index b18f3d3..a072ae6 100644 --- a/dist/ba_data/python/ba/_account.py +++ b/dist/ba_data/python/ba/_account.py @@ -11,7 +11,7 @@ from typing import TYPE_CHECKING import _ba if TYPE_CHECKING: - from typing import Any, Optional, Dict, List, Tuple + from typing import Any, Optional import ba @@ -24,16 +24,16 @@ class AccountSubsystem: """ def __init__(self) -> None: - self.account_tournament_list: Optional[Tuple[int, List[str]]] = None + self.account_tournament_list: Optional[tuple[int, list[str]]] = None # FIXME: should abstract/structure these. - self.tournament_info: Dict = {} - self.league_rank_cache: Dict = {} + self.tournament_info: dict = {} + self.league_rank_cache: dict = {} self.last_post_purchase_message_time: Optional[float] = None # If we try to run promo-codes due to launch-args/etc we might # not be signed in yet; go ahead and queue them up in that case. - self.pending_promo_codes: List[str] = [] + self.pending_promo_codes: list[str] = [] def on_app_launch(self) -> None: """Called when the app is done bootstrapping.""" @@ -74,7 +74,7 @@ class AccountSubsystem: return self.league_rank_cache.get('info', None) def get_league_rank_points(self, - data: Optional[Dict[str, Any]], + data: Optional[dict[str, Any]], subset: str = None) -> int: """(internal)""" if data is None: @@ -121,7 +121,7 @@ class AccountSubsystem: def cache_tournament_info(self, info: Any) -> None: """(internal)""" - from ba._enums import TimeType, TimeFormat + from ba._generated.enums import TimeType, TimeFormat for entry in info: cache_entry = self.tournament_info[entry['tournamentID']] = ( copy.deepcopy(entry)) @@ -132,7 +132,7 @@ class AccountSubsystem: TimeFormat.MILLISECONDS) cache_entry['valid'] = True - def get_purchased_icons(self) -> List[str]: + def get_purchased_icons(self) -> list[str]: """(internal)""" # pylint: disable=cyclic-import from ba import _store @@ -206,7 +206,7 @@ class AccountSubsystem: def show_post_purchase_message(self) -> None: """(internal)""" from ba._language import Lstr - from ba._enums import TimeType + 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): @@ -237,7 +237,7 @@ class AccountSubsystem: def add_pending_promo_code(self, code: str) -> None: """(internal)""" from ba._language import Lstr - from ba._enums import TimeType + from ba._generated.enums import TimeType # If we're not signed in, queue up the code to run the next time we # are and issue a warning if we haven't signed in within the next diff --git a/dist/ba_data/python/ba/_achievement.py b/dist/ba_data/python/ba/_achievement.py index 8775313..13634cf 100644 --- a/dist/ba_data/python/ba/_achievement.py +++ b/dist/ba_data/python/ba/_achievement.py @@ -9,7 +9,7 @@ import _ba from ba._error import print_exception if TYPE_CHECKING: - from typing import Any, Sequence, List, Dict, Union, Optional, Tuple, Set + from typing import Any, Sequence, Union, Optional import ba # This could use some cleanup. @@ -71,11 +71,11 @@ class AchievementSubsystem: """ def __init__(self) -> None: - self.achievements: List[Achievement] = [] - self.achievements_to_display: (List[Tuple[ba.Achievement, bool]]) = [] + self.achievements: list[Achievement] = [] + self.achievements_to_display: (list[tuple[ba.Achievement, bool]]) = [] self.achievement_display_timer: Optional[_ba.Timer] = None self.last_achievement_display_time: float = 0.0 - self.achievement_completion_banner_slots: Set[int] = set() + self.achievement_completion_banner_slots: set[int] = set() self._init_achievements() def _init_achievements(self) -> None: @@ -374,7 +374,7 @@ class AchievementSubsystem: return achs[0] def achievements_for_coop_level(self, - level_name: str) -> List[Achievement]: + level_name: str) -> list[Achievement]: """Given a level name, return achievements available for it.""" # For the Easy campaign we return achievements for the Default @@ -388,7 +388,7 @@ class AchievementSubsystem: def _test(self) -> None: """For testing achievement animations.""" - from ba._enums import TimeType + from ba._generated.enums import TimeType def testcall1() -> None: self.achievements[0].announce_completion() @@ -489,7 +489,7 @@ class Achievement: def announce_completion(self, sound: bool = True) -> None: """Kick off an announcement for this achievement's completion.""" - from ba._enums import TimeType + from ba._generated.enums import TimeType app = _ba.app # Even though there are technically achievements when we're not @@ -612,14 +612,14 @@ class Achievement: delay: float, outdelay: float = None, color: Sequence[float] = None, - style: str = 'post_game') -> List[ba.Actor]: + style: str = 'post_game') -> list[ba.Actor]: """Create a display for the Achievement. Shows the Achievement icon, name, and description. """ # pylint: disable=cyclic-import from ba._language import Lstr - from ba._enums import SpecialChar + from ba._generated.enums import SpecialChar from ba._coopsession import CoopSession from bastd.actor.image import Image from bastd.actor.text import Text @@ -663,7 +663,7 @@ class Achievement: print_exception('Error determining campaign.') hmo = False - objs: List[ba.Actor] + objs: list[ba.Actor] if in_game_colors: objs = [] @@ -898,12 +898,12 @@ class Achievement: transition_out_delay=None).autoretain()) return objs - def _getconfig(self) -> Dict[str, Any]: + def _getconfig(self) -> dict[str, Any]: """ 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( + val: dict[str, Any] = (_ba.app.config.setdefault( 'Achievements', {}).setdefault(self._name, {'Complete': False})) assert isinstance(val, dict) return val @@ -923,7 +923,7 @@ class Achievement: from ba._general import WeakCall from ba._language import Lstr from ba._messages import DieMessage - from ba._enums import TimeType, SpecialChar + from ba._generated.enums import TimeType, SpecialChar app = _ba.app app.ach.last_achievement_display_time = _ba.time(TimeType.REAL) @@ -971,7 +971,7 @@ class Achievement: i += 1 assert self._completion_banner_slot is not None y_offs = 110 * self._completion_banner_slot - objs: List[ba.Actor] = [] + objs: list[ba.Actor] = [] obj = Image(_ba.gettexture('shadow'), position=(-30, 30 + y_offs), front=True, diff --git a/dist/ba_data/python/ba/_activity.py b/dist/ba_data/python/ba/_activity.py index 2304ca9..701de8e 100644 --- a/dist/ba_data/python/ba/_activity.py +++ b/dist/ba_data/python/ba/_activity.py @@ -16,8 +16,7 @@ from ba._general import Call, verify_object_death from ba._messages import UNHANDLED if TYPE_CHECKING: - from weakref import ReferenceType - from typing import Optional, Type, Any, Dict, List + from typing import Optional, Any import ba from bastd.actor.respawnicon import RespawnIcon @@ -58,9 +57,9 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): # pylint: disable=too-many-public-methods # Annotating attr types at the class level lets us introspect at runtime. - settings_raw: Dict[str, Any] - teams: List[TeamType] - players: List[PlayerType] + settings_raw: dict[str, Any] + teams: list[TeamType] + players: list[PlayerType] # Whether to print every time a player dies. This can be pertinent # in games such as Death-Match but can be annoying in games where it @@ -112,6 +111,11 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): # transitions). inherits_tint = False + # Whether players should be allowed to join in the middle of this + # activity. Note that Sessions may not allow mid-activity-joins even + # if the activity says its ok. + allow_mid_activity_joins: bool = True + # If the activity fades or transitions in, it should set the length of # time here so that previous activities will be kept alive for that # long (avoiding 'holes' in the screen) @@ -145,8 +149,8 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): # Player/Team types should have been specified as type args; # grab those. - self._playertype: Type[PlayerType] - self._teamtype: Type[TeamType] + self._playertype: type[PlayerType] + self._teamtype: type[TeamType] self._setup_player_and_team_types() # FIXME: Relocate or remove the need for this stuff. @@ -155,7 +159,7 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): self._session = weakref.ref(_ba.getsession()) # Preloaded data for actors, maps, etc; indexed by type. - self.preloads: Dict[Type, Any] = {} + self.preloads: dict[type, Any] = {} # Hopefully can eventually kill this; activities should # validate/store whatever settings they need at init time @@ -167,17 +171,17 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): self._has_ended = False self._activity_death_check_timer: Optional[ba.Timer] = None self._expired = False - self._delay_delete_players: List[PlayerType] = [] - self._delay_delete_teams: List[TeamType] = [] - self._players_that_left: List[ReferenceType[PlayerType]] = [] - self._teams_that_left: List[ReferenceType[TeamType]] = [] + self._delay_delete_players: list[PlayerType] = [] + self._delay_delete_teams: list[TeamType] = [] + self._players_that_left: list[weakref.ref[PlayerType]] = [] + self._teams_that_left: list[weakref.ref[TeamType]] = [] self._transitioning_out = False # A handy place to put most actors; this list is pruned of dead # actors regularly and these actors are insta-killed as the activity # is dying. - self._actor_refs: List[ba.Actor] = [] - self._actor_weak_refs: List[ReferenceType[ba.Actor]] = [] + self._actor_refs: list[ba.Actor] = [] + self._actor_weak_refs: list[weakref.ref[ba.Actor]] = [] self._last_prune_dead_actors_time = _ba.time() self._prune_dead_actors_timer: Optional[ba.Timer] = None @@ -257,12 +261,12 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): return self._expired @property - def playertype(self) -> Type[PlayerType]: + def playertype(self) -> type[PlayerType]: """The type of ba.Player this Activity is using.""" return self._playertype @property - def teamtype(self) -> Type[TeamType]: + def teamtype(self) -> type[TeamType]: """The type of ba.Team this Activity is using.""" return self._teamtype @@ -275,7 +279,7 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): (internal) """ - from ba._enums import TimeType + from ba._generated.enums import TimeType # Create a real-timer that watches a weak-ref of this activity # and reports any lingering references keeping it alive. @@ -704,8 +708,8 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): assert issubclass(self._teamtype, Team) @classmethod - def _check_activity_death(cls, activity_ref: ReferenceType[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 diff --git a/dist/ba_data/python/ba/_activitytypes.py b/dist/ba_data/python/ba/_activitytypes.py index 1498f89..85b81b0 100644 --- a/dist/ba_data/python/ba/_activitytypes.py +++ b/dist/ba_data/python/ba/_activitytypes.py @@ -8,13 +8,13 @@ from typing import TYPE_CHECKING import _ba from ba._activity import Activity from ba._music import setmusic, MusicType -from ba._enums import InputType, UIScale +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 if TYPE_CHECKING: - from typing import Any, Dict, Optional + from typing import Optional import ba from ba._lobby import JoinInfo diff --git a/dist/ba_data/python/ba/_actor.py b/dist/ba_data/python/ba/_actor.py index acab1b3..53cdca2 100644 --- a/dist/ba_data/python/ba/_actor.py +++ b/dist/ba_data/python/ba/_actor.py @@ -7,15 +7,15 @@ from __future__ import annotations import weakref from typing import TYPE_CHECKING, TypeVar, overload +import _ba from ba._messages import DieMessage, DeathType, OutOfBoundsMessage, UNHANDLED from ba._error import print_exception, ActivityNotFoundError -import _ba if TYPE_CHECKING: from typing import Any, Optional, Literal import ba -T = TypeVar('T', bound='Actor') +TA = TypeVar('TA', bound='Actor') class Actor: @@ -94,7 +94,7 @@ class Actor: return UNHANDLED - def autoretain(self: T) -> T: + def autoretain(self: TA) -> TA: """Keep this Actor alive without needing to hold a reference to it. This keeps the ba.Actor in existence by storing a reference to it diff --git a/dist/ba_data/python/ba/_ads.py b/dist/ba_data/python/ba/_ads.py index 7493979..e591bbb 100644 --- a/dist/ba_data/python/ba/_ads.py +++ b/dist/ba_data/python/ba/_ads.py @@ -33,7 +33,7 @@ class AdsSubsystem: def do_remove_in_game_ads_message(self) -> None: """(internal)""" from ba._language import Lstr - from ba._enums import TimeType + from ba._generated.enums import TimeType # Print this message once every 10 minutes at most. tval = _ba.time(TimeType.REAL) @@ -70,7 +70,7 @@ class AdsSubsystem: # pylint: disable=too-many-statements # pylint: disable=too-many-branches # pylint: disable=too-many-locals - from ba._enums import TimeType + from ba._generated.enums import TimeType app = _ba.app show = True diff --git a/dist/ba_data/python/ba/_app.py b/dist/ba_data/python/ba/_app.py index 2a4f2c4..b6d2e1a 100644 --- a/dist/ba_data/python/ba/_app.py +++ b/dist/ba_data/python/ba/_app.py @@ -21,7 +21,7 @@ from ba._net import NetworkSubsystem if TYPE_CHECKING: import ba from bastd.actor import spazappearance - from typing import Optional, Dict, Set, Any, Type, Tuple, Callable, List + from typing import Optional, Any, Callable class App: @@ -167,7 +167,7 @@ class App: return self._env['vr_mode'] @property - def ui_bounds(self) -> Tuple[float, float, float, float]: + def ui_bounds(self) -> tuple[float, float, float, float]: """Bounds of the 'safe' screen area in ui space. This tuple contains: (x-min, x-max, y-min, y-max) @@ -208,7 +208,7 @@ class App: self.allow_ticket_purchases: bool = not self.iircade_mode # Misc. - self.tips: List[str] = [] + self.tips: list[str] = [] self.stress_test_reset_timer: Optional[ba.Timer] = None self.did_weak_call_warning = False @@ -225,7 +225,7 @@ class App: self.input_map_hash: Optional[str] = None # Co-op Campaigns. - self.campaigns: Dict[str, ba.Campaign] = {} + self.campaigns: dict[str, ba.Campaign] = {} # Server Mode. self.server: Optional[ba.ServerController] = None @@ -250,31 +250,32 @@ class App: self.main_menu_last_news_fetch_time: Optional[float] = None # Spaz. - self.spaz_appearances: Dict[str, spazappearance.Appearance] = {} + self.spaz_appearances: dict[str, spazappearance.Appearance] = {} self.last_spaz_turbo_warn_time: float = -99999.0 # Maps. - self.maps: Dict[str, Type[ba.Map]] = {} + self.maps: dict[str, type[ba.Map]] = {} # Gameplay. self.teams_series_length = 7 self.ffa_series_length = 24 - self.coop_session_args: Dict = {} + self.coop_session_args: dict = {} self.value_test_defaults: dict = {} self.first_main_menu = True # FIXME: Move to mainmenu class. self.did_menu_intro = False # FIXME: Move to mainmenu class. self.main_menu_window_refresh_check_count = 0 # FIXME: Mv to mainmenu. self.main_menu_resume_callbacks: list = [] # Can probably go away. - self.special_offer: Optional[Dict] = None + self.special_offer: Optional[dict] = None self.ping_thread_count = 0 - self.invite_confirm_windows: List[Any] = [] # FIXME: Don't use Any. - self.store_layout: Optional[Dict[str, List[Dict[str, Any]]]] = None - self.store_items: Optional[Dict[str, Dict]] = None + self.invite_confirm_windows: list[Any] = [] # FIXME: Don't use Any. + self.store_layout: Optional[dict[str, list[dict[str, Any]]]] = None + self.store_items: Optional[dict[str, dict]] = None self.pro_sale_start_time: Optional[int] = None self.pro_sale_start_val: Optional[int] = None self.delegate: Optional[ba.AppDelegate] = None + self._asyncio_timer: Optional[ba.Timer] = None def on_app_launch(self) -> None: """Runs after the app finishes bootstrapping. @@ -290,9 +291,10 @@ class App: from bastd import appdelegate from bastd import maps as stdmaps from bastd.actor import spazappearance - from ba._enums import TimeType + from ba._generated.enums import TimeType import custom_hooks custom_hooks.on_app_launch() + cfg = self.config self.delegate = appdelegate.AppDelegate() @@ -518,7 +520,7 @@ class App: def launch_coop_game(self, game: str, force: bool = False, - args: Dict = None) -> bool: + args: dict = None) -> bool: """High level way to launch a local co-op session.""" # pylint: disable=cyclic-import from ba._campaign import getcampaign @@ -582,7 +584,8 @@ class App: """ import urllib.request try: - val = urllib.request.urlopen('https://example.com').read() + with urllib.request.urlopen('https://example.com') as url: + val = url.read() print('HTTPS TEST SUCCESS', len(val)) except Exception as exc: print('HTTPS TEST FAIL:', exc) diff --git a/dist/ba_data/python/ba/_appconfig.py b/dist/ba_data/python/ba/_appconfig.py index ceb7c75..611483e 100644 --- a/dist/ba_data/python/ba/_appconfig.py +++ b/dist/ba_data/python/ba/_appconfig.py @@ -8,7 +8,7 @@ from typing import TYPE_CHECKING import _ba if TYPE_CHECKING: - from typing import Any, List, Tuple + from typing import Any class AppConfig(dict): @@ -57,7 +57,7 @@ class AppConfig(dict): """ return _ba.get_appconfig_default_value(key) - def builtin_keys(self) -> List[str]: + def builtin_keys(self) -> list[str]: """Return the list of valid key names recognized by ba.AppConfig. This set of keys can be used with resolve(), default_value(), etc. @@ -93,11 +93,11 @@ class AppConfig(dict): self.commit() -def read_config() -> Tuple[AppConfig, bool]: +def read_config() -> tuple[AppConfig, bool]: """Read the game config.""" import os import json - from ba._enums import TimeType + from ba._generated.enums import TimeType config_file_healthy = False @@ -107,7 +107,7 @@ def read_config() -> Tuple[AppConfig, bool]: config_contents = '' try: if os.path.exists(config_file_path): - with open(config_file_path) as infile: + with open(config_file_path, encoding='utf-8') as infile: config_contents = infile.read() config = AppConfig(json.loads(config_contents)) else: @@ -140,7 +140,7 @@ def read_config() -> Tuple[AppConfig, bool]: prev_path = config_file_path + '.prev' try: if os.path.exists(prev_path): - with open(prev_path) as infile: + with open(prev_path, encoding='utf-8') as infile: config_contents = infile.read() config = AppConfig(json.loads(config_contents)) else: diff --git a/dist/ba_data/python/ba/_appdelegate.py b/dist/ba_data/python/ba/_appdelegate.py index f6282be..1262870 100644 --- a/dist/ba_data/python/ba/_appdelegate.py +++ b/dist/ba_data/python/ba/_appdelegate.py @@ -6,7 +6,7 @@ from __future__ import annotations from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import Type, Optional, Any, Dict, Callable + from typing import Optional, Callable import ba @@ -17,8 +17,8 @@ class AppDelegate: """ def create_default_game_settings_ui( - self, gameclass: Type[ba.GameActivity], - sessiontype: Type[ba.Session], settings: Optional[dict], + self, gameclass: type[ba.GameActivity], + sessiontype: type[ba.Session], settings: Optional[dict], completion_call: Callable[[Optional[dict]], None]) -> None: """Launch a UI to configure the given game config. diff --git a/dist/ba_data/python/ba/_apputils.py b/dist/ba_data/python/ba/_apputils.py index 8e3767c..ea88890 100644 --- a/dist/ba_data/python/ba/_apputils.py +++ b/dist/ba_data/python/ba/_apputils.py @@ -10,7 +10,7 @@ from typing import TYPE_CHECKING import _ba if TYPE_CHECKING: - from typing import List, Any, Callable, Optional + from typing import Any import ba @@ -57,7 +57,7 @@ def handle_log() -> None: after a short bit if desired. """ from ba._net import master_server_post - from ba._enums import TimeType + from ba._generated.enums import TimeType app = _ba.app app.log_have_new = True if not app.log_upload_timer_started: @@ -124,7 +124,7 @@ def handle_leftover_log_file() -> None: from ba._net import master_server_post if os.path.exists(_ba.get_log_file_path()): - with open(_ba.get_log_file_path()) as infile: + with open(_ba.get_log_file_path(), encoding='utf-8') as infile: info = json.loads(infile.read()) infile.close() do_send = should_submit_debug_info() @@ -186,9 +186,9 @@ def print_live_object_warnings(when: Any, from ba._actor import Actor from ba._activity import Activity - sessions: List[ba.Session] = [] - activities: List[ba.Activity] = [] - actors: List[ba.Actor] = [] + sessions: list[ba.Session] = [] + activities: list[ba.Activity] = [] + actors: list[ba.Actor] = [] # Once we come across leaked stuff, printing again is probably # redundant. @@ -225,7 +225,7 @@ def print_live_object_warnings(when: Any, def print_corrupt_file_error() -> None: """Print an error if a corrupt file is found.""" from ba._general import Call - from ba._enums import TimeType + from ba._generated.enums import TimeType _ba.timer(2.0, lambda: _ba.screenmessage( _ba.app.lang.get_resource('internal.corruptFileText'). diff --git a/dist/ba_data/python/ba/_assetmanager.py b/dist/ba_data/python/ba/_assetmanager.py index c23624a..c12f96d 100644 --- a/dist/ba_data/python/ba/_assetmanager.py +++ b/dist/ba_data/python/ba/_assetmanager.py @@ -4,7 +4,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Annotated +from dataclasses import dataclass, field from pathlib import Path import threading import urllib.request @@ -14,21 +15,26 @@ import time import os import sys -from efro import entity +from efro.dataclassio import (ioprepped, IOAttrs, dataclass_from_json, + dataclass_to_json) if TYPE_CHECKING: from bacommon.assets import AssetPackageFlavor - from typing import List -class FileValue(entity.CompoundValue): +@ioprepped +@dataclass +class FileValue: """State for an individual file.""" -class State(entity.Entity): +@ioprepped +@dataclass +class State: """Holds all persistent state for the asset-manager.""" - files = entity.CompoundDictField('files', str, FileValue()) + files: Annotated[dict[str, FileValue], + IOAttrs('files')] = field(default_factory=dict) class AssetManager: @@ -54,7 +60,7 @@ class AssetManager: def launch_gather( self, - packages: List[str], + packages: list[str], flavor: AssetPackageFlavor, account_token: str, ) -> AssetGather: @@ -101,8 +107,8 @@ class AssetManager: try: state_path = self.state_path if state_path.exists(): - with open(self.state_path) as infile: - self._state = State.from_json_str(infile.read()) + with open(self.state_path, encoding='utf-8') as infile: + self._state = dataclass_from_json(State, infile.read()) return except Exception: logging.exception('Error loading existing AssetManager state') @@ -113,8 +119,8 @@ class AssetManager: print('ASSET-MANAGER SAVING STATE') try: - with open(self.state_path, 'w') as outfile: - outfile.write(self._state.to_json_str()) + with open(self.state_path, 'w', encoding='utf-8') as outfile: + outfile.write(dataclass_to_json(self._state)) except Exception: logging.exception('Error writing AssetManager state') diff --git a/dist/ba_data/python/ba/_asyncio.py b/dist/ba_data/python/ba/_asyncio.py new file mode 100644 index 0000000..76206e6 --- /dev/null +++ b/dist/ba_data/python/ba/_asyncio.py @@ -0,0 +1,71 @@ +# Released under the MIT License. See LICENSE for details. +# +"""Asyncio related functionality. + +Exploring the idea of allowing Python coroutines to run gracefully +besides our internal event loop. They could prove useful for networking +operations or possibly game logic. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING +import asyncio + +if TYPE_CHECKING: + from typing import Optional + import ba + +# Our timer and event loop for the ballistica game thread. +_asyncio_timer: Optional[ba.Timer] = None +_asyncio_event_loop: Optional[asyncio.AbstractEventLoop] = None + + +def setup_asyncio() -> None: + """Setup asyncio functionality for our game thread.""" + # pylint: disable=global-statement + + import _ba + from ba._generated.enums import TimeType + + assert _ba.in_game_thread() + + # Create our event-loop. We don't expect there to be one + # running on this thread before we do. + try: + asyncio.get_running_loop() + print('Found running asyncio loop; unexpected.') + except RuntimeError: + pass + + global _asyncio_event_loop # pylint: disable=invalid-name + _asyncio_event_loop = asyncio.new_event_loop() + + # Ideally we should integrate asyncio into our C++ Thread class's + # low level event loop so that asyncio timers/sockets/etc. could + # be true first-class citizens. For now, though, we can explicitly + # pump an asyncio loop periodically which gets us a decent + # approximation of that, which should be good enough for + # all but extremely time sensitive uses. + # See https://stackoverflow.com/questions/29782377/ + # is-it-possible-to-run-only-a-single-step-of-the-asyncio-event-loop + def run_cycle() -> None: + assert _asyncio_event_loop is not None + _asyncio_event_loop.call_soon(_asyncio_event_loop.stop) + _asyncio_event_loop.run_forever() + + global _asyncio_timer # pylint: disable=invalid-name + _asyncio_timer = _ba.Timer(1.0 / 30.0, + run_cycle, + timetype=TimeType.REAL, + repeat=True) + + async def aio_test() -> None: + print('TEST AIO TASK STARTING') + assert _asyncio_event_loop is not None + assert asyncio.get_running_loop() is _asyncio_event_loop + await asyncio.sleep(2.0) + print('TEST AIO TASK ENDING') + + if bool(False): + _asyncio_event_loop.create_task(aio_test()) diff --git a/dist/ba_data/python/ba/_benchmark.py b/dist/ba_data/python/ba/_benchmark.py index 070b022..119296c 100644 --- a/dist/ba_data/python/ba/_benchmark.py +++ b/dist/ba_data/python/ba/_benchmark.py @@ -9,7 +9,7 @@ from typing import TYPE_CHECKING import _ba if TYPE_CHECKING: - from typing import Dict, Any, Sequence + from typing import Any, Sequence import ba @@ -57,7 +57,7 @@ def run_stress_test(playlist_type: str = 'Random', """Run a stress test.""" from ba import modutils from ba._general import Call - from ba._enums import TimeType + from ba._generated.enums import TimeType _ba.screenmessage( 'Beginning stress test.. use ' "'End Game' to stop testing.", @@ -88,12 +88,12 @@ def stop_stress_test() -> None: _ba.app.stress_test_reset_timer = None -def start_stress_test(args: Dict[str, Any]) -> None: +def start_stress_test(args: dict[str, Any]) -> None: """(internal)""" from ba._general import Call from ba._dualteamsession import DualTeamSession from ba._freeforallsession import FreeForAllSession - from ba._enums import TimeType, TimeFormat + from ba._generated.enums import TimeType, TimeFormat appconfig = _ba.app.config playlist_type = args['playlist_type'] if playlist_type == 'Random': @@ -125,9 +125,9 @@ def start_stress_test(args: Dict[str, Any]) -> None: timeformat=TimeFormat.MILLISECONDS) -def _reset_stress_test(args: Dict[str, Any]) -> None: +def _reset_stress_test(args: dict[str, Any]) -> None: from ba._general import Call - from ba._enums import TimeType + 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() @@ -144,7 +144,7 @@ def run_gpu_benchmark() -> None: def run_media_reload_benchmark() -> None: """Kick off a benchmark to test media reloading speeds.""" from ba._general import Call - from ba._enums import TimeType + from ba._generated.enums import TimeType _ba.reload_media() _ba.show_progress_bar() diff --git a/dist/ba_data/python/ba/_campaign.py b/dist/ba_data/python/ba/_campaign.py index 77f4ce9..e7f5315 100644 --- a/dist/ba_data/python/ba/_campaign.py +++ b/dist/ba_data/python/ba/_campaign.py @@ -8,7 +8,7 @@ from typing import TYPE_CHECKING import _ba if TYPE_CHECKING: - from typing import Any, List, Dict + from typing import Any import ba @@ -30,7 +30,7 @@ class Campaign: def __init__(self, name: str, sequential: bool = True): self._name = name - self._levels: List[ba.Level] = [] + self._levels: list[ba.Level] = [] self._sequential = sequential @property @@ -51,7 +51,7 @@ class Campaign: self._levels.append(level) @property - def levels(self) -> List[ba.Level]: + def levels(self) -> list[ba.Level]: """The list of ba.Levels in the Campaign.""" return self._levels @@ -80,9 +80,9 @@ class Campaign: return self.configdict.get('Selection', self._levels[0].name) @property - def configdict(self) -> Dict[str, Any]: + def configdict(self) -> dict[str, Any]: """Return the live config dict for this campaign.""" - val: Dict[str, Any] = (_ba.app.config.setdefault('Campaigns', + val: dict[str, Any] = (_ba.app.config.setdefault('Campaigns', {}).setdefault( self._name, {})) assert isinstance(val, dict) diff --git a/dist/ba_data/python/ba/_coopgame.py b/dist/ba_data/python/ba/_coopgame.py index ae7adca..1d771b5 100644 --- a/dist/ba_data/python/ba/_coopgame.py +++ b/dist/ba_data/python/ba/_coopgame.py @@ -10,7 +10,7 @@ from ba._gameactivity import GameActivity from ba._general import WeakCall if TYPE_CHECKING: - from typing import Type, Dict, Any, Set, List, Sequence, Optional + from typing import Any, Sequence, Optional from bastd.actor.playerspaz import PlayerSpaz import ba @@ -28,7 +28,7 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]): session: ba.CoopSession @classmethod - def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool: + def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool: from ba._coopsession import CoopSession return issubclass(sessiontype, CoopSession) @@ -36,7 +36,7 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]): super().__init__(settings) # Cache these for efficiency. - self._achievements_awarded: Set[str] = set() + self._achievements_awarded: set[str] = set() self._life_warning_beep: Optional[ba.Actor] = None self._life_warning_beep_timer: Optional[ba.Timer] = None @@ -62,15 +62,15 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]): _ba.get_scores_to_beat(levelname, config_str, WeakCall(self._on_got_scores_to_beat)) - def _on_got_scores_to_beat(self, scores: List[Dict[str, Any]]) -> None: + def _on_got_scores_to_beat(self, scores: list[dict[str, Any]]) -> None: pass def _show_standard_scores_to_beat_ui(self, - scores: List[Dict[str, Any]]) -> None: + scores: list[dict[str, Any]]) -> None: from efro.util import asserttype from ba._gameutils import timestring, animate from ba._nodeactor import NodeActor - from ba._enums import TimeFormat + from ba._generated.enums import TimeFormat display_type = self.get_score_type() if scores is not None: diff --git a/dist/ba_data/python/ba/_coopsession.py b/dist/ba_data/python/ba/_coopsession.py index 6273f86..1dab1e1 100644 --- a/dist/ba_data/python/ba/_coopsession.py +++ b/dist/ba_data/python/ba/_coopsession.py @@ -9,7 +9,7 @@ import _ba from ba._session import Session if TYPE_CHECKING: - from typing import Any, List, Dict, Optional, Callable, Sequence + from typing import Any, Optional, Callable, Sequence import ba TEAM_COLORS = [(0.2, 0.4, 1.6)] @@ -77,7 +77,7 @@ class CoopSession(Session): self._ran_tutorial_activity = False self._tutorial_activity: Optional[ba.Activity] = None - self._custom_menu_ui: List[Dict[str, Any]] = [] + self._custom_menu_ui: list[dict[str, Any]] = [] # Start our joining screen. self.setactivity(_ba.newactivity(CoopJoinActivity)) @@ -90,6 +90,16 @@ class CoopSession(Session): """Get the game instance currently being played.""" return self._current_game_instance + def should_allow_mid_activity_joins(self, activity: ba.Activity) -> bool: + # pylint: disable=cyclic-import + from ba._gameactivity import GameActivity + + # Disallow any joins in the middle of the game. + if isinstance(activity, GameActivity): + return False + + return True + def _update_on_deck_game_instances(self) -> None: # pylint: disable=cyclic-import from ba._gameactivity import GameActivity @@ -149,42 +159,51 @@ class CoopSession(Session): from bastd.tutorial import TutorialActivity self._tutorial_activity = _ba.newactivity(TutorialActivity) - def get_custom_menu_entries(self) -> List[Dict[str, Any]]: + def get_custom_menu_entries(self) -> list[dict[str, Any]]: return self._custom_menu_ui def on_player_leave(self, sessionplayer: ba.SessionPlayer) -> None: from ba._general import WeakCall super().on_player_leave(sessionplayer) - # If all our players leave we wanna quit out of the session. - _ba.timer(2.0, WeakCall(self._end_session_if_empty)) + _ba.timer(2.0, WeakCall(self._handle_empty_activity)) - def _end_session_if_empty(self) -> None: + def _handle_empty_activity(self) -> None: + """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? - # If there's still players in the current activity, we're good. + # If there are still players in the current activity, we're good. if activity.players: return - # If there's *no* players left in the current activity but there *is* - # in the session, restart the activity to pull them into the game - # (or quit if they're just in the lobby). + # If there are *not* players in the current activity but there + # *are* in the session: if not activity.players and self.sessionplayers: - # Special exception for tourney games; don't auto-restart these. - if self.tournament_id is not None: - self.end() - else: - # Don't restart joining activities; this probably means there's - # someone with a chooser up in that case. - if not activity.is_joining_activity: + # If we're in a game, we should restart to pull in players + # currently waiting in the session. + if isinstance(activity, GameActivity): + + # Never restart tourney games however; just end the session + # if all players are gone. + if self.tournament_id is not None: + self.end() + else: self.restart() - # Hmm; no players anywhere. lets just end the session. + # Hmm; no players anywhere. Let's end the entire session if we're + # running a GUI (or just the current game if we're running headless). else: - self.end() + if not _ba.app.headless_mode: + self.end() + else: + if isinstance(activity, GameActivity): + with _ba.Context(activity): + activity.end_game() def _on_tournament_restart_menu_press( self, resume_callback: Callable[[], Any]) -> None: @@ -248,12 +267,14 @@ class CoopSession(Session): else: outcome = '' if results is None else results.get('outcome', '') - # If at any point we have no in-game players, quit out of the session - # (this can happen if someone leaves in the tutorial for instance). - active_players = [p for p in self.sessionplayers if p.in_game] - if not active_players: - self.end() - return + # If we're running with a gui and at any point we have no + # in-game players, quit out of the session (this can happen if + # someone leaves in the tutorial for instance). + if not _ba.app.headless_mode: + active_players = [p for p in self.sessionplayers if p.in_game] + if not active_players: + self.end() + return # If we're in a between-round activity or a restart-activity, # hop into a round. @@ -320,7 +341,7 @@ class CoopSession(Session): self.setactivity(_ba.newactivity(TransitionActivity)) else: - playerinfos: List[ba.PlayerInfo] + playerinfos: list[ba.PlayerInfo] # Generic team games. if isinstance(results, GameResults): diff --git a/dist/ba_data/python/ba/_dependency.py b/dist/ba_data/python/ba/_dependency.py index 6cb43e8..b6663a0 100644 --- a/dist/ba_data/python/ba/_dependency.py +++ b/dist/ba_data/python/ba/_dependency.py @@ -10,8 +10,7 @@ from typing import (Generic, TypeVar, TYPE_CHECKING) import _ba if TYPE_CHECKING: - from typing import Optional, Any, Dict, List, Set, Type - from weakref import ReferenceType + from typing import Optional, Any import ba T = TypeVar('T', bound='DependencyComponent') @@ -32,13 +31,13 @@ class Dependency(Generic[T]): methods via self.floofcls(). """ - def __init__(self, cls: Type[T], config: Any = None): + def __init__(self, cls: type[T], config: Any = None): """Instantiate a Dependency given a ba.DependencyComponent type. Optionally, an arbitrary object can be passed as 'config' to influence dependency calculation for the target class. """ - self.cls: Type[T] = cls + self.cls: type[T] = cls self.config = config self._hash: Optional[int] = None @@ -91,7 +90,7 @@ class DependencyComponent: category: Dependency Classes """ - _dep_entry: ReferenceType[DependencyEntry] + _dep_entry: weakref.ref[DependencyEntry] def __init__(self) -> None: """Instantiate a DependencyComponent.""" @@ -110,7 +109,7 @@ class DependencyComponent: return True @classmethod - def get_dynamic_deps(cls, config: Any = None) -> List[Dependency]: + def get_dynamic_deps(cls, config: Any = None) -> list[Dependency]: """Return any dynamically-calculated deps for this component/config. Deps declared statically as part of the class do not need to be @@ -180,7 +179,7 @@ class DependencySet(Generic[T]): self._loaded = False # Dependency data indexed by hash. - self.entries: Dict[int, DependencyEntry] = {} + self.entries: dict[int, DependencyEntry] = {} # def __del__(self) -> None: # print("~DepSet()") @@ -220,12 +219,12 @@ class DependencySet(Generic[T]): """Whether this set has been successfully resolved.""" return self._resolved - def get_asset_package_ids(self) -> Set[str]: + def get_asset_package_ids(self) -> set[str]: """Return the set of asset-package-ids required by this dep-set. Must be called on a resolved dep-set. """ - ids: Set[str] = set() + ids: set[str] = set() if not self._resolved: raise Exception('Must be called on a resolved dep-set.') for entry in self.entries.values(): diff --git a/dist/ba_data/python/ba/_error.py b/dist/ba_data/python/ba/_error.py index ea98f3f..c9e75fc 100644 --- a/dist/ba_data/python/ba/_error.py +++ b/dist/ba_data/python/ba/_error.py @@ -9,7 +9,7 @@ from typing import TYPE_CHECKING import _ba if TYPE_CHECKING: - from typing import Any, List + from typing import Any import ba @@ -21,12 +21,12 @@ class DependencyError(Exception): (this will generally be missing assets). """ - def __init__(self, deps: List[ba.Dependency]): + def __init__(self, deps: list[ba.Dependency]): super().__init__() self._deps = deps @property - def deps(self) -> List[ba.Dependency]: + def deps(self) -> list[ba.Dependency]: """The list of missing dependencies causing this error.""" return self._deps diff --git a/dist/ba_data/python/ba/_freeforallsession.py b/dist/ba_data/python/ba/_freeforallsession.py index e81eb47..c0ceee9 100644 --- a/dist/ba_data/python/ba/_freeforallsession.py +++ b/dist/ba_data/python/ba/_freeforallsession.py @@ -10,7 +10,6 @@ import _ba from ba._multiteamsession import MultiTeamSession if TYPE_CHECKING: - from typing import Dict import ba @@ -25,12 +24,12 @@ class FreeForAllSession(MultiTeamSession): _playlist_randomize_var = 'Free-for-All Playlist Randomize' _playlists_var = 'Free-for-All Playlists' - def get_ffa_point_awards(self) -> Dict[int, int]: + def get_ffa_point_awards(self) -> dict[int, int]: """Return the number of points awarded for different rankings. This is based on the current number of players. """ - point_awards: Dict[int, int] + point_awards: dict[int, int] if len(self.sessionplayers) == 1: point_awards = {} elif len(self.sessionplayers) == 2: diff --git a/dist/ba_data/python/ba/_gameactivity.py b/dist/ba_data/python/ba/_gameactivity.py index 1c1e7fb..c7d633e 100644 --- a/dist/ba_data/python/ba/_gameactivity.py +++ b/dist/ba_data/python/ba/_gameactivity.py @@ -19,8 +19,7 @@ from ba._player import PlayerInfo from ba import _map if TYPE_CHECKING: - from typing import (List, Optional, Dict, Type, Any, Callable, Sequence, - Tuple, Union) + from typing import Optional, Any, Callable, Sequence, Union from bastd.actor.playerspaz import PlayerSpaz from bastd.actor.bomb import TNTSpawner import ba @@ -37,7 +36,7 @@ class GameActivity(Activity[PlayerType, TeamType]): # pylint: disable=too-many-public-methods # Tips to be presented to the user at the start of the game. - tips: List[Union[str, ba.GameTip]] = [] + tips: list[Union[str, ba.GameTip]] = [] # Default getname() will return this if not None. name: Optional[str] = None @@ -46,7 +45,7 @@ class GameActivity(Activity[PlayerType, TeamType]): description: Optional[str] = None # Default get_available_settings() will return this if not None. - available_settings: Optional[List[ba.Setting]] = None + available_settings: Optional[list[ba.Setting]] = None # Default getscoreconfig() will return this if not None. scoreconfig: Optional[ba.ScoreConfig] = None @@ -65,7 +64,7 @@ class GameActivity(Activity[PlayerType, TeamType]): @classmethod def create_settings_ui( cls, - sessiontype: Type[ba.Session], + sessiontype: type[ba.Session], settings: Optional[dict], completion_call: Callable[[Optional[dict]], None], ) -> None: @@ -104,7 +103,7 @@ class GameActivity(Activity[PlayerType, TeamType]): return cls.name if cls.name is not None else 'Untitled Game' @classmethod - def get_display_string(cls, settings: Optional[Dict] = None) -> ba.Lstr: + def get_display_string(cls, settings: Optional[dict] = None) -> ba.Lstr: """Return a descriptive name for this game/settings combo. Subclasses should override getname(); not this. @@ -130,7 +129,7 @@ class GameActivity(Activity[PlayerType, TeamType]): return Lstr(translate=('teamNames', name)) @classmethod - def get_description(cls, sessiontype: Type[ba.Session]) -> str: + def get_description(cls, sessiontype: type[ba.Session]) -> str: """Get a str description of this game type. The default implementation simply returns the 'description' class var. @@ -142,7 +141,7 @@ 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. @@ -152,7 +151,7 @@ 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. """ @@ -160,7 +159,7 @@ class GameActivity(Activity[PlayerType, TeamType]): return [] if cls.available_settings is None else cls.available_settings @classmethod - def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]: + def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]: """ Called by the default ba.GameActivity.create_settings_ui() implementation; should return a list of map names valid @@ -170,7 +169,7 @@ class GameActivity(Activity[PlayerType, TeamType]): return _map.getmaps('melee') @classmethod - def get_settings_display_string(cls, config: Dict[str, Any]) -> ba.Lstr: + def get_settings_display_string(cls, config: dict[str, Any]) -> ba.Lstr: """Given a game config dict, return a short description for it. This is used when viewing game-lists or showing what game @@ -200,7 +199,7 @@ class GameActivity(Activity[PlayerType, TeamType]): return sval @classmethod - def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool: + def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool: """Return whether this game supports the provided Session type.""" from ba._multiteamsession import MultiTeamSession @@ -213,7 +212,7 @@ class GameActivity(Activity[PlayerType, TeamType]): # Holds some flattened info about the player set at the point # when on_begin() is called. - self.initialplayerinfos: Optional[List[ba.PlayerInfo]] = None + self.initialplayerinfos: Optional[list[ba.PlayerInfo]] = None # Go ahead and get our map loading. self._map_type = _map.get_map_class(self._calc_map_name(settings)) @@ -222,7 +221,7 @@ class GameActivity(Activity[PlayerType, TeamType]): self._map_type.preload() self._map: Optional[ba.Map] = None self._powerup_drop_timer: Optional[ba.Timer] = None - self._tnt_spawners: Optional[Dict[int, TNTSpawner]] = None + self._tnt_spawners: Optional[dict[int, TNTSpawner]] = None self._tnt_drop_timer: Optional[ba.Timer] = None self._game_scoreboard_name_text: Optional[ba.Actor] = None self._game_scoreboard_description_text: Optional[ba.Actor] = None @@ -235,7 +234,7 @@ class GameActivity(Activity[PlayerType, TeamType]): self._tournament_time_limit_title_text: Optional[ba.NodeActor] = None self._tournament_time_limit_text: Optional[ba.NodeActor] = None self._tournament_time_limit_text_input: Optional[ba.NodeActor] = None - self._zoom_message_times: Dict[int, float] = {} + self._zoom_message_times: dict[int, float] = {} self._is_waiting_for_continue = False self._continue_cost = _ba.get_account_misc_read_val( @@ -385,7 +384,7 @@ class GameActivity(Activity[PlayerType, TeamType]): # pylint: disable=cyclic-import from bastd.ui.continues import ContinuesWindow from ba._coopsession import CoopSession - from ba._enums import TimeType + from ba._generated.enums import TimeType try: if _ba.get_account_misc_read_val('enableContinues', False): @@ -459,7 +458,7 @@ class GameActivity(Activity[PlayerType, TeamType]): callback=WeakCall(self._on_tournament_query_response), ) - def _on_tournament_query_response(self, data: Optional[Dict[str, + def _on_tournament_query_response(self, data: Optional[dict[str, Any]]) -> None: if data is not None: data_t = data['t'] # This used to be the whole payload. @@ -653,7 +652,7 @@ class GameActivity(Activity[PlayerType, TeamType]): def _show_tip(self) -> None: # pylint: disable=too-many-locals from ba._gameutils import animate, GameTip - from ba._enums import SpecialChar + from ba._generated.enums import SpecialChar # If there's any tips left on the list, display one. if self.tips: @@ -1009,7 +1008,7 @@ class GameActivity(Activity[PlayerType, TeamType]): If the time-limit expires, end_game() will be called. """ from ba._nodeactor import NodeActor - from ba._enums import TimeType + from ba._generated.enums import TimeType if duration <= 0.0: return self._tournament_time_limit = int(duration) @@ -1154,7 +1153,7 @@ class GameActivity(Activity[PlayerType, TeamType]): # If settings doesn't specify a map, pick a random one from the # list of supported ones. unowned_maps = _map.get_unowned_maps() - valid_maps: List[str] = [ + valid_maps: list[str] = [ m for m in self.get_supported_maps(type(self.session)) if m not in unowned_maps ] diff --git a/dist/ba_data/python/ba/_gameresults.py b/dist/ba_data/python/ba/_gameresults.py index 9885b30..f4b3ebd 100644 --- a/dist/ba_data/python/ba/_gameresults.py +++ b/dist/ba_data/python/ba/_gameresults.py @@ -12,8 +12,7 @@ from efro.util import asserttype from ba._team import Team, SessionTeam if TYPE_CHECKING: - from weakref import ReferenceType - from typing import Sequence, Tuple, Any, Optional, Dict, List, Union + from typing import Sequence, Optional import ba @@ -36,11 +35,10 @@ class GameResults: def __init__(self) -> None: self._game_set = False - self._scores: Dict[int, Tuple[ReferenceType[ba.SessionTeam], + self._scores: dict[int, tuple[weakref.ref[ba.SessionTeam], Optional[int]]] = {} - self._sessionteams: Optional[List[ReferenceType[ - ba.SessionTeam]]] = None - self._playerinfos: Optional[List[ba.PlayerInfo]] = None + self._sessionteams: Optional[list[weakref.ref[ba.SessionTeam]]] = None + self._playerinfos: Optional[list[ba.PlayerInfo]] = None self._lower_is_better: Optional[bool] = None self._score_label: Optional[str] = None self._none_is_winner: Optional[bool] = None @@ -83,7 +81,7 @@ class GameResults: return None @property - def sessionteams(self) -> List[ba.SessionTeam]: + def sessionteams(self) -> list[ba.SessionTeam]: """Return all ba.SessionTeams in the results.""" if not self._game_set: raise RuntimeError("Can't get teams until game is set.") @@ -107,7 +105,7 @@ class GameResults: """ from ba._gameutils import timestring from ba._language import Lstr - from ba._enums import TimeFormat + 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.") @@ -127,7 +125,7 @@ class GameResults: return Lstr(value='-') @property - def playerinfos(self) -> List[ba.PlayerInfo]: + def playerinfos(self) -> list[ba.PlayerInfo]: """Get info about the players represented by the results.""" if not self._game_set: raise RuntimeError("Can't get player-info until game is set.") @@ -169,13 +167,13 @@ class GameResults: return None @property - def winnergroups(self) -> List[WinnerGroup]: + def winnergroups(self) -> list[WinnerGroup]: """Get an ordered list of winner groups.""" if not self._game_set: raise RuntimeError("Can't get winners until game is set.") # Group by best scoring teams. - winners: Dict[int, List[ba.SessionTeam]] = {} + winners: dict[int, list[ba.SessionTeam]] = {} scores = [ score for score in self._scores.values() if score[0]() is not None and score[1] is not None @@ -186,13 +184,13 @@ class GameResults: team = score[0]() assert team is not None sval.append(team) - results: List[Tuple[Optional[int], - List[ba.SessionTeam]]] = list(winners.items()) + results: list[tuple[Optional[int], + 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] = [] + none_sessionteams: list[ba.SessionTeam] = [] for score in self._scores.values(): scoreteam = score[0]() if scoreteam is not None and score[1] is None: @@ -201,7 +199,7 @@ class GameResults: # Add the Nones to the list (either as winners or losers # depending on the rules). if none_sessionteams: - nones: List[Tuple[Optional[int], List[ba.SessionTeam]]] = [ + nones: list[tuple[Optional[int], list[ba.SessionTeam]]] = [ (None, none_sessionteams) ] if self._none_is_winner: diff --git a/dist/ba_data/python/ba/_gameutils.py b/dist/ba_data/python/ba/_gameutils.py index 89e7354..d337f3d 100644 --- a/dist/ba_data/python/ba/_gameutils.py +++ b/dist/ba_data/python/ba/_gameutils.py @@ -8,11 +8,11 @@ from dataclasses import dataclass from typing import TYPE_CHECKING import _ba -from ba._enums import TimeType, TimeFormat, SpecialChar, UIScale +from ba._generated.enums import TimeType, TimeFormat, SpecialChar, UIScale from ba._error import ActivityNotFoundError if TYPE_CHECKING: - from typing import Any, Dict, Sequence, Optional + from typing import Sequence, Optional import ba TROPHY_CHARS = { @@ -45,7 +45,7 @@ def get_trophy_string(trophy_id: str) -> str: def animate(node: ba.Node, attr: str, - keys: Dict[float, float], + keys: dict[float, float], loop: bool = False, offset: float = 0, timetype: ba.TimeType = TimeType.SIM, @@ -119,7 +119,7 @@ def animate(node: ba.Node, def animate_array(node: ba.Node, attr: str, size: int, - keys: Dict[float, Sequence[float]], + keys: dict[float, Sequence[float]], loop: bool = False, offset: float = 0, timetype: ba.TimeType = TimeType.SIM, @@ -301,6 +301,7 @@ def timestring(timeval: float, # 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) if sval >= 0.005 or not bits: bits.append('${S}') diff --git a/dist/ba_data/python/ba/_general.py b/dist/ba_data/python/ba/_general.py index 57ad9a8..e919138 100644 --- a/dist/ba_data/python/ba/_general.py +++ b/dist/ba_data/python/ba/_general.py @@ -13,13 +13,12 @@ from typing import TYPE_CHECKING, TypeVar, Protocol from efro.terminal import Clr import _ba from ba._error import print_error, print_exception -from ba._enums import TimeType +from ba._generated.enums import TimeType if TYPE_CHECKING: from types import FrameType - from typing import Any, Type, Optional + from typing import Any, Optional from efro.call import Call as Call # 'as Call' so we re-export. - from weakref import ReferenceType class Existable(Protocol): @@ -57,7 +56,7 @@ def existing(obj: Optional[ExistableType]) -> Optional[ExistableType]: return obj if obj is not None and obj.exists() else None -def getclass(name: str, subclassof: Type[T]) -> Type[T]: +def getclass(name: str, subclassof: type[T]) -> type[T]: """Given a full class name such as foo.bar.MyClass, return the class. Category: General Utility Functions @@ -70,7 +69,7 @@ def getclass(name: str, subclassof: Type[T]) -> Type[T]: modulename = '.'.join(splits[:-1]) classname = splits[-1] module = importlib.import_module(modulename) - cls: Type = getattr(module, classname) + cls: type = getattr(module, classname) if not issubclass(cls, subclassof): raise TypeError(f'{name} is not a subclass of {subclassof}.') @@ -133,7 +132,7 @@ def print_refs(obj: Any) -> None: i += 1 -def get_type_name(cls: Type) -> str: +def get_type_name(cls: type) -> str: """Return a full type name including module for a class.""" return cls.__module__ + '.' + cls.__name__ @@ -343,7 +342,7 @@ def print_active_refs(obj: Any) -> None: f' {ref4}{Clr.RST}') -def _verify_object_death(wref: ReferenceType) -> None: +def _verify_object_death(wref: weakref.ref) -> None: obj = wref() if obj is None: return diff --git a/dist/ba_data/python/ba/_generated/__init__.py b/dist/ba_data/python/ba/_generated/__init__.py new file mode 100644 index 0000000..5a0fc5f --- /dev/null +++ b/dist/ba_data/python/ba/_generated/__init__.py @@ -0,0 +1,2 @@ +# Released under the MIT License. See LICENSE for details. +# diff --git a/dist/ba_data/python/ba/_generated/__pycache__/__init__.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/_generated/__pycache__/__init__.cpython-38.opt-1.pyc new file mode 100644 index 0000000..ced1cae Binary files /dev/null and b/dist/ba_data/python/ba/_generated/__pycache__/__init__.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/_generated/__pycache__/__init__.cpython-38.pyc b/dist/ba_data/python/ba/_generated/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..e68eb74 Binary files /dev/null and b/dist/ba_data/python/ba/_generated/__pycache__/__init__.cpython-38.pyc differ diff --git a/dist/ba_data/python/ba/_generated/__pycache__/__init__.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/_generated/__pycache__/__init__.cpython-39.opt-1.pyc new file mode 100644 index 0000000..6574b7f Binary files /dev/null and b/dist/ba_data/python/ba/_generated/__pycache__/__init__.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/_generated/__pycache__/__init__.cpython-39.pyc b/dist/ba_data/python/ba/_generated/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..4dc6d1a Binary files /dev/null and b/dist/ba_data/python/ba/_generated/__pycache__/__init__.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/_generated/__pycache__/enums.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/_generated/__pycache__/enums.cpython-38.opt-1.pyc new file mode 100644 index 0000000..4432ed7 Binary files /dev/null and b/dist/ba_data/python/ba/_generated/__pycache__/enums.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/__pycache__/_enums.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/_generated/__pycache__/enums.cpython-38.pyc similarity index 94% rename from dist/ba_data/python/ba/__pycache__/_enums.cpython-38.opt-1.pyc rename to dist/ba_data/python/ba/_generated/__pycache__/enums.cpython-38.pyc index 4260545..665e4a4 100644 Binary files a/dist/ba_data/python/ba/__pycache__/_enums.cpython-38.opt-1.pyc and b/dist/ba_data/python/ba/_generated/__pycache__/enums.cpython-38.pyc differ diff --git a/dist/ba_data/python/ba/_generated/__pycache__/enums.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/_generated/__pycache__/enums.cpython-39.opt-1.pyc new file mode 100644 index 0000000..cd74180 Binary files /dev/null and b/dist/ba_data/python/ba/_generated/__pycache__/enums.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/_generated/__pycache__/enums.cpython-39.pyc b/dist/ba_data/python/ba/_generated/__pycache__/enums.cpython-39.pyc new file mode 100644 index 0000000..07f0eca Binary files /dev/null and b/dist/ba_data/python/ba/_generated/__pycache__/enums.cpython-39.pyc differ diff --git a/dist/ba_data/python/ba/_generated/enums.py b/dist/ba_data/python/ba/_generated/enums.py new file mode 100644 index 0000000..ea937b5 --- /dev/null +++ b/dist/ba_data/python/ba/_generated/enums.py @@ -0,0 +1,198 @@ +# Released under the MIT License. See LICENSE for details. +"""Enum vals generated by batools.pythonenumsmodule; do not edit by hand.""" + +from enum import Enum + + +class InputType(Enum): + """Types of input a controller can send to the game. + + Category: Enums + + """ + UP_DOWN = 2 + LEFT_RIGHT = 3 + JUMP_PRESS = 4 + JUMP_RELEASE = 5 + PUNCH_PRESS = 6 + PUNCH_RELEASE = 7 + BOMB_PRESS = 8 + BOMB_RELEASE = 9 + PICK_UP_PRESS = 10 + PICK_UP_RELEASE = 11 + RUN = 12 + FLY_PRESS = 13 + FLY_RELEASE = 14 + START_PRESS = 15 + START_RELEASE = 16 + HOLD_POSITION_PRESS = 17 + HOLD_POSITION_RELEASE = 18 + LEFT_PRESS = 19 + LEFT_RELEASE = 20 + RIGHT_PRESS = 21 + RIGHT_RELEASE = 22 + UP_PRESS = 23 + UP_RELEASE = 24 + DOWN_PRESS = 25 + DOWN_RELEASE = 26 + + +class UIScale(Enum): + """The overall scale the UI is being rendered for. Note that this is + independent of pixel resolution. For example, a phone and a desktop PC + might render the game at similar pixel resolutions but the size they + display content at will vary significantly. + + Category: Enums + + 'large' is used for devices such as desktop PCs where fine details can + be clearly seen. UI elements are generally smaller on the screen + and more content can be seen at once. + + 'medium' is used for devices such as tablets, TVs, or VR headsets. + This mode strikes a balance between clean readability and amount of + content visible. + + 'small' is used primarily for phones or other small devices where + content needs to be presented as large and clear in order to remain + readable from an average distance. + """ + LARGE = 0 + MEDIUM = 1 + SMALL = 2 + + +class TimeType(Enum): + """Specifies the type of time for various operations to target/use. + + Category: Enums + + 'sim' time is the local simulation time for an activity or session. + It can proceed at different rates depending on game speed, stops + for pauses, etc. + + 'base' is the baseline time for an activity or session. It proceeds + consistently regardless of game speed or pausing, but may stop during + occurrences such as network outages. + + 'real' time is mostly based on clock time, with a few exceptions. It may + 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 + + +class TimeFormat(Enum): + """Specifies the format time values are provided in. + + Category: Enums + """ + SECONDS = 0 + MILLISECONDS = 1 + + +class Permission(Enum): + """Permissions that can be requested from the OS. + + Category: Enums + """ + STORAGE = 0 + + +class SpecialChar(Enum): + """Special characters the game can print. + + Category: Enums + """ + DOWN_ARROW = 0 + UP_ARROW = 1 + LEFT_ARROW = 2 + RIGHT_ARROW = 3 + TOP_BUTTON = 4 + LEFT_BUTTON = 5 + RIGHT_BUTTON = 6 + BOTTOM_BUTTON = 7 + DELETE = 8 + SHIFT = 9 + BACK = 10 + LOGO_FLAT = 11 + REWIND_BUTTON = 12 + PLAY_PAUSE_BUTTON = 13 + FAST_FORWARD_BUTTON = 14 + DPAD_CENTER_BUTTON = 15 + OUYA_BUTTON_O = 16 + OUYA_BUTTON_U = 17 + OUYA_BUTTON_Y = 18 + OUYA_BUTTON_A = 19 + OUYA_LOGO = 20 + LOGO = 21 + TICKET = 22 + GOOGLE_PLAY_GAMES_LOGO = 23 + GAME_CENTER_LOGO = 24 + DICE_BUTTON1 = 25 + DICE_BUTTON2 = 26 + DICE_BUTTON3 = 27 + DICE_BUTTON4 = 28 + GAME_CIRCLE_LOGO = 29 + PARTY_ICON = 30 + TEST_ACCOUNT = 31 + TICKET_BACKING = 32 + TROPHY1 = 33 + TROPHY2 = 34 + TROPHY3 = 35 + TROPHY0A = 36 + TROPHY0B = 37 + TROPHY4 = 38 + LOCAL_ACCOUNT = 39 + ALIBABA_LOGO = 40 + FLAG_UNITED_STATES = 41 + FLAG_MEXICO = 42 + FLAG_GERMANY = 43 + FLAG_BRAZIL = 44 + FLAG_RUSSIA = 45 + FLAG_CHINA = 46 + FLAG_UNITED_KINGDOM = 47 + FLAG_CANADA = 48 + FLAG_INDIA = 49 + FLAG_JAPAN = 50 + FLAG_FRANCE = 51 + FLAG_INDONESIA = 52 + FLAG_ITALY = 53 + FLAG_SOUTH_KOREA = 54 + FLAG_NETHERLANDS = 55 + FEDORA = 56 + HAL = 57 + CROWN = 58 + YIN_YANG = 59 + EYE_BALL = 60 + SKULL = 61 + HEART = 62 + DRAGON = 63 + HELMET = 64 + MUSHROOM = 65 + NINJA_STAR = 66 + VIKING_HELMET = 67 + MOON = 68 + SPIDER = 69 + FIREBALL = 70 + FLAG_UNITED_ARAB_EMIRATES = 71 + FLAG_QATAR = 72 + FLAG_EGYPT = 73 + FLAG_KUWAIT = 74 + FLAG_ALGERIA = 75 + FLAG_SAUDI_ARABIA = 76 + FLAG_MALAYSIA = 77 + FLAG_CZECH_REPUBLIC = 78 + FLAG_AUSTRALIA = 79 + FLAG_SINGAPORE = 80 + OCULUS_LOGO = 81 + STEAM_LOGO = 82 + NVIDIA_LOGO = 83 + FLAG_IRAN = 84 + FLAG_POLAND = 85 + FLAG_ARGENTINA = 86 + FLAG_PHILIPPINES = 87 + FLAG_CHILE = 88 + MIKIROG = 89 diff --git a/dist/ba_data/python/ba/_hooks.py b/dist/ba_data/python/ba/_hooks.py index d47126e..1c2bfd9 100644 --- a/dist/ba_data/python/ba/_hooks.py +++ b/dist/ba_data/python/ba/_hooks.py @@ -18,10 +18,23 @@ from typing import TYPE_CHECKING import _ba if TYPE_CHECKING: - from typing import List, Sequence, Optional, Dict, Any + from typing import Sequence, Optional, Any import ba +def finish_bootstrapping() -> None: + """Do final bootstrapping related bits.""" + from ba._asyncio import setup_asyncio + assert _ba.in_game_thread() + + # Kick off our asyncio event handling, allowing us to use coroutines + # in our game thread alongside our internal event handling. + setup_asyncio() + + # Ok, bootstrapping is done; time to get the show started. + _ba.app.on_app_launch() + + def reset_to_main_menu() -> None: """Reset the game to the main menu gracefully.""" _ba.app.return_to_main_menu_session_gracefully() @@ -319,6 +332,31 @@ def filter_chat_message(msg: str, client_id: int) -> Optional[str]: return chooks.filter_chat_message(msg,client_id) +def kick_vote_started(by:str,to:str) -> None: + """ + get account ids of who started kick vote for whom , + do what ever u want logging to files , whatever. + """ + print(by+">"+to) + +def on_kicked(account_id:str) -> None: + pass + # print(account_id+" kicked ...sad") + +def on_kick_vote_end() -> None: + pass + # print("kick vote end") + +from tools import servercheck +def on_player_join(pb_id:str)-> None: + servercheck.on_player_join(pb_id) + pass + # print(pb_id+" joined python layer") + +def on_player_leave(pb_id:str)-> None: + pass + # + print(pb_id+" left python layer") def local_chat_message(msg: str) -> None: if (_ba.app.ui.party_window is not None @@ -326,7 +364,7 @@ def local_chat_message(msg: str) -> None: _ba.app.ui.party_window().on_chat_message(msg) -def get_player_icon(sessionplayer: ba.SessionPlayer) -> Dict[str, Any]: +def get_player_icon(sessionplayer: ba.SessionPlayer) -> dict[str, Any]: info = sessionplayer.get_icon_info() return { 'texture': _ba.gettexture(info['texture']), diff --git a/dist/ba_data/python/ba/_input.py b/dist/ba_data/python/ba/_input.py index bd318ca..fcf96ef 100644 --- a/dist/ba_data/python/ba/_input.py +++ b/dist/ba_data/python/ba/_input.py @@ -8,7 +8,7 @@ from typing import TYPE_CHECKING import _ba if TYPE_CHECKING: - from typing import Any, Dict, Tuple + from typing import Any import ba @@ -601,14 +601,14 @@ def get_input_map_hash(inputdevice: ba.InputDevice) -> str: def get_input_device_config(device: ba.InputDevice, - default: bool) -> Tuple[Dict, str]: + 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. """ cfg = _ba.app.config name = device.name - ccfgs: Dict[str, Any] = cfg.setdefault('Controllers', {}) + ccfgs: dict[str, Any] = cfg.setdefault('Controllers', {}) ccfgs.setdefault(name, {}) unique_id = device.unique_identifier if default: diff --git a/dist/ba_data/python/ba/_keyboard.py b/dist/ba_data/python/ba/_keyboard.py index 258125f..245cbea 100644 --- a/dist/ba_data/python/ba/_keyboard.py +++ b/dist/ba_data/python/ba/_keyboard.py @@ -7,7 +7,7 @@ from __future__ import annotations from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import List, Tuple, Dict + pass class Keyboard: @@ -30,6 +30,6 @@ class Keyboard: """ name: str - chars: List[Tuple[str, ...]] - pages: Dict[str, Tuple[str, ...]] - nums: Tuple[str, ...] + chars: list[tuple[str, ...]] + pages: dict[str, tuple[str, ...]] + nums: tuple[str, ...] diff --git a/dist/ba_data/python/ba/_language.py b/dist/ba_data/python/ba/_language.py index 98f0da5..5709b6e 100644 --- a/dist/ba_data/python/ba/_language.py +++ b/dist/ba_data/python/ba/_language.py @@ -11,7 +11,7 @@ import _ba if TYPE_CHECKING: import ba - from typing import Any, Dict, List, Optional, Tuple, Union, Sequence + from typing import Any, Optional, Union, Sequence class LanguageSubsystem: @@ -37,7 +37,7 @@ class LanguageSubsystem: # We don't yet support full unicode display on windows or linux :-(. if (language in { 'Chinese', 'ChineseTraditional', 'Persian', 'Korean', 'Arabic', - 'Hindi', 'Vietnamese' + 'Hindi', 'Vietnamese', 'Thai', 'Tamil' } and not _ba.can_display_full_unicode()): return False return True @@ -79,12 +79,14 @@ class LanguageSubsystem: 'ar': 'Arabic', 'zh': 'Chinese', 'tr': 'Turkish', + 'th': 'Thai', 'id': 'Indonesian', 'sr': 'Serbian', 'uk': 'Ukrainian', 'vi': 'Vietnamese', 'vec': 'Venetian', - 'hi': 'Hindi' + 'hi': 'Hindi', + 'ta': 'Tamil', } # Special case for Chinese: map specific variations to traditional. @@ -108,7 +110,7 @@ class LanguageSubsystem: return _ba.app.config.get('Lang', self.default_language) @property - def available_languages(self) -> List[str]: + def available_languages(self) -> list[str]: """A list of all available languages. Note that languages that may be present in game assets but which @@ -161,7 +163,8 @@ class LanguageSubsystem: else: switched = False - with open('ba_data/data/languages/english.json') as infile: + with open('ba_data/data/languages/english.json', + encoding='utf-8') as infile: lenglishvalues = json.loads(infile.read()) # None implies default. @@ -173,7 +176,7 @@ class LanguageSubsystem: else: lmodfile = 'ba_data/data/languages/' + language.lower( ) + '.json' - with open(lmodfile) as infile: + with open(lmodfile, encoding='utf-8') as infile: lmodvalues = json.loads(infile.read()) except Exception: from ba import _error @@ -400,7 +403,7 @@ class Lstr: resource: str, fallback_resource: str = '', fallback_value: str = '', - subs: Sequence[Tuple[str, Union[str, Lstr]]] = []) -> None: + subs: Sequence[tuple[str, Union[str, Lstr]]] = []) -> None: """Create an Lstr from a string resource.""" ... @@ -408,8 +411,8 @@ class Lstr: @overload def __init__(self, *, - translate: Tuple[str, str], - subs: Sequence[Tuple[str, Union[str, Lstr]]] = []) -> None: + translate: tuple[str, str], + subs: Sequence[tuple[str, Union[str, Lstr]]] = []) -> None: """Create an Lstr by translating a string in a category.""" ... @@ -418,7 +421,7 @@ class Lstr: def __init__(self, *, value: str, - subs: Sequence[Tuple[str, Union[str, Lstr]]] = []) -> None: + subs: Sequence[tuple[str, Union[str, Lstr]]] = []) -> None: """Create an Lstr from a raw string value.""" ... @@ -529,7 +532,7 @@ class Lstr: return lstr -def _add_to_attr_dict(dst: AttrDict, src: Dict) -> None: +def _add_to_attr_dict(dst: AttrDict, src: dict) -> None: for key, value in list(src.items()): if isinstance(value, dict): try: diff --git a/dist/ba_data/python/ba/_level.py b/dist/ba_data/python/ba/_level.py index c45f715..53e67f1 100644 --- a/dist/ba_data/python/ba/_level.py +++ b/dist/ba_data/python/ba/_level.py @@ -10,8 +10,7 @@ from typing import TYPE_CHECKING import _ba if TYPE_CHECKING: - from weakref import ReferenceType - from typing import Type, Any, Dict, Optional + from typing import Any, Optional import ba @@ -23,7 +22,7 @@ class Level: def __init__(self, name: str, - gametype: Type[ba.GameActivity], + gametype: type[ba.GameActivity], settings: dict, preview_texture_name: str, displayname: str = None): @@ -32,16 +31,20 @@ class Level: self._settings = settings self._preview_texture_name = preview_texture_name self._displayname = displayname - self._campaign: Optional[ReferenceType[ba.Campaign]] = None + self._campaign: Optional[weakref.ref[ba.Campaign]] = None self._index: Optional[int] = None self._score_version_string: Optional[str] = None + def __repr__(self) -> str: + cls = type(self) + return f"<{cls.__module__}.{cls.__name__} '{self._name}'>" + @property def name(self) -> str: """The unique name for this Level.""" return self._name - def get_settings(self) -> Dict[str, Any]: + def get_settings(self) -> dict[str, Any]: """Returns the settings for this Level.""" settings = copy.deepcopy(self._settings) @@ -70,7 +73,7 @@ class Level: self._gametype.get_display_string(self._settings))]) @property - def gametype(self) -> Type[ba.GameActivity]: + def gametype(self) -> type[ba.GameActivity]: """The type of game used for this Level.""" return self._gametype @@ -113,7 +116,7 @@ class Level: return {} return copy.deepcopy(config[high_scores_key]) - def set_high_scores(self, high_scores: Dict) -> None: + def set_high_scores(self, high_scores: dict) -> None: """Set high scores for this level.""" config = self._get_config_dict() high_scores_key = 'High Scores' + self.get_score_version_string() @@ -144,7 +147,7 @@ class Level: config = self._get_config_dict() config['Rating'] = max(old_rating, rating) - def _get_config_dict(self) -> Dict[str, Any]: + def _get_config_dict(self) -> dict[str, Any]: """Return/create the persistent state dict for this level. The referenced dict exists under the game's config dict and @@ -153,7 +156,7 @@ 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, { + val: dict[str, Any] = configdict.setdefault(self._name, { 'Rating': 0.0, 'Complete': False }) diff --git a/dist/ba_data/python/ba/_lobby.py b/dist/ba_data/python/ba/_lobby.py index 313b6ad..f063f53 100644 --- a/dist/ba_data/python/ba/_lobby.py +++ b/dist/ba_data/python/ba/_lobby.py @@ -12,11 +12,11 @@ import _ba from ba._error import print_exception, print_error, NotFoundError from ba._gameutils import animate, animate_array from ba._language import Lstr -from ba._enums import SpecialChar, InputType +from ba._generated.enums import SpecialChar, InputType from ba._profile import get_player_profile_colors if TYPE_CHECKING: - from typing import Optional, List, Dict, Any, Sequence, Union + from typing import Optional, Any, Sequence, Union import ba MAX_QUICK_CHANGE_COUNT = 30 @@ -152,11 +152,11 @@ class Chooser: self._dead = False self._text_node: Optional[ba.Node] = None self._profilename = '' - self._profilenames: List[str] = [] + self._profilenames: list[str] = [] self._ready: bool = False - self._character_names: List[str] = [] + self._character_names: list[str] = [] self._last_change: Sequence[Union[float, int]] = (0, 0) - self._profiles: Dict[str, Dict[str, Any]] = {} + self._profiles: dict[str, dict[str, Any]] = {} app = _ba.app @@ -835,11 +835,11 @@ class Lobby: self._dummy_teams = SessionTeam() self._sessionteams = [weakref.ref(self._dummy_teams)] v_offset = (-150 if isinstance(session, CoopSession) else -50) - self.choosers: List[Chooser] = [] + self.choosers: list[Chooser] = [] self.base_v_offset = v_offset self.update_positions() self._next_add_team = 0 - self.character_names_local_unlocked: List[str] = [] + self.character_names_local_unlocked: list[str] = [] self._vpos = 0 # Grab available profiles. @@ -861,7 +861,7 @@ class Lobby: return self._use_team_colors @property - def sessionteams(self) -> List[ba.SessionTeam]: + def sessionteams(self) -> list[ba.SessionTeam]: """ba.SessionTeams available in this lobby.""" allteams = [] for tref in self._sessionteams: @@ -870,7 +870,7 @@ class Lobby: allteams.append(team) return allteams - def get_choosers(self) -> List[Chooser]: + def get_choosers(self) -> list[Chooser]: """Return the lobby's current choosers.""" return self.choosers diff --git a/dist/ba_data/python/ba/_map.py b/dist/ba_data/python/ba/_map.py index c2f9ee9..b1eba27 100644 --- a/dist/ba_data/python/ba/_map.py +++ b/dist/ba_data/python/ba/_map.py @@ -11,7 +11,7 @@ from ba import _math from ba._actor import Actor if TYPE_CHECKING: - from typing import Set, List, Type, Optional, Sequence, Any, Tuple + from typing import Optional, Sequence, Any import ba @@ -52,7 +52,7 @@ def get_map_display_string(name: str) -> ba.Lstr: return _language.Lstr(translate=('mapsNames', name)) -def getmaps(playtype: str) -> List[str]: +def getmaps(playtype: str) -> list[str]: """Return a list of ba.Map types supporting a playtype str. Category: Asset Functions @@ -101,13 +101,13 @@ def getmaps(playtype: str) -> List[str]: if playtype in val.get_play_types()) -def get_unowned_maps() -> List[str]: +def get_unowned_maps() -> list[str]: """Return the list of local maps not owned by the current account. Category: Asset Functions """ from ba import _store - unowned_maps: Set[str] = set() + unowned_maps: set[str] = set() if not _ba.app.headless_mode: for map_section in _store.get_store_layout()['maps']: for mapitem in map_section['items']: @@ -117,7 +117,7 @@ def get_unowned_maps() -> List[str]: return sorted(unowned_maps) -def get_map_class(name: str) -> Type[ba.Map]: +def get_map_class(name: str) -> type[ba.Map]: """Return a map type given a name. Category: Asset Functions @@ -140,7 +140,7 @@ class Map(Actor): """ defs: Any = None name = 'Map' - _playtypes: List[str] = [] + _playtypes: list[str] = [] @classmethod def preload(cls) -> None: @@ -156,7 +156,7 @@ class Map(Actor): activity.preloads[cls] = cls.on_preload() @classmethod - def get_play_types(cls) -> List[str]: + def get_play_types(cls) -> list[str]: """Return valid play types for this map.""" return [] @@ -213,21 +213,6 @@ class Map(Actor): from tools import textonmap textonmap.textonmap() - - - self.hg=ba.NodeActor( - _ba.newnode('text', - attrs={ - 'text': "Smoothy Build\n v1.2", - - 'flatness': 1.0, - 'h_align': 'center', - 'v_attach':'bottom', - 'h_attach':'right', - 'scale':0.7, - 'position':(-60,23), - 'color':(0.3,0.3,0.3) - })) # Set area-of-interest bounds. aoi_bounds = self.get_def_bound_box('area_of_interest_bounds') if aoi_bounds is None: @@ -314,7 +299,7 @@ class Map(Actor): def get_def_bound_box( self, name: str - ) -> Optional[Tuple[float, float, float, float, float, float]]: + ) -> Optional[tuple[float, float, float, float, float, float]]: """Return a 6 member bounds tuple or None if it is not defined.""" try: box = self.defs.boxes[name] @@ -330,7 +315,7 @@ class Map(Actor): return (None if val is None else _math.vec3validate(val) if __debug__ else val) - def get_def_points(self, name: str) -> List[Sequence[float]]: + def get_def_points(self, name: str) -> list[Sequence[float]]: """Return a list of named points. Return as many sequential ones are defined (flag1, flag2, flag3), etc. @@ -426,7 +411,7 @@ class Map(Actor): return None -def register_map(maptype: Type[Map]) -> None: +def register_map(maptype: type[Map]) -> None: """Register a map class with the game.""" if maptype.name in _ba.app.maps: raise RuntimeError('map "' + maptype.name + '" already registered') diff --git a/dist/ba_data/python/ba/_math.py b/dist/ba_data/python/ba/_math.py index 98834ee..856d426 100644 --- a/dist/ba_data/python/ba/_math.py +++ b/dist/ba_data/python/ba/_math.py @@ -8,7 +8,7 @@ from collections import abc from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import Tuple, Sequence + from typing import Sequence def vec3validate(value: Sequence[float]) -> Sequence[float]: @@ -45,7 +45,7 @@ def is_point_in_box(pnt: Sequence[float], box: Sequence[float]) -> bool: and (abs(pnt[2] - box[2]) <= box[8] * 0.5)) -def normalized_color(color: Sequence[float]) -> Tuple[float, ...]: +def normalized_color(color: Sequence[float]) -> tuple[float, ...]: """Scale a color so its largest value is 1; useful for coloring lights. category: General Utility Functions diff --git a/dist/ba_data/python/ba/_messages.py b/dist/ba_data/python/ba/_messages.py index c57ea55..a871d4b 100644 --- a/dist/ba_data/python/ba/_messages.py +++ b/dist/ba_data/python/ba/_messages.py @@ -11,7 +11,7 @@ from enum import Enum import _ba if TYPE_CHECKING: - from typing import Sequence, Optional, Type, Any + from typing import Sequence, Optional, Any import ba @@ -105,7 +105,7 @@ class PlayerDiedMessage: self.how = how def getkillerplayer(self, - playertype: Type[PlayerType]) -> Optional[PlayerType]: + playertype: type[PlayerType]) -> Optional[PlayerType]: """Return the ba.Player responsible for the killing, if any. Pass the Player type being used by the current game. @@ -113,7 +113,7 @@ class PlayerDiedMessage: assert isinstance(self._killerplayer, (playertype, type(None))) return self._killerplayer - def getplayer(self, playertype: Type[PlayerType]) -> PlayerType: + def getplayer(self, playertype: type[PlayerType]) -> PlayerType: """Return the ba.Player that died. The type of player for the current activity should be passed so that @@ -294,7 +294,7 @@ class HitMessage: if force_direction is not None else velocity) def get_source_player( - self, playertype: Type[PlayerType]) -> Optional[PlayerType]: + self, playertype: type[PlayerType]) -> Optional[PlayerType]: """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 0386552..7776a5b 100644 --- a/dist/ba_data/python/ba/_meta.py +++ b/dist/ba_data/python/ba/_meta.py @@ -14,7 +14,7 @@ from dataclasses import dataclass, field import _ba if TYPE_CHECKING: - from typing import Dict, List, Tuple, Union, Optional, Type, Set + from typing import Union, Optional import ba # The meta api version of this build of the game. @@ -27,9 +27,9 @@ CURRENT_API_VERSION = 6 @dataclass class ScanResults: """Final results from a metadata scan.""" - games: List[str] = field(default_factory=list) - plugins: List[str] = field(default_factory=list) - keyboards: List[str] = field(default_factory=list) + games: list[str] = field(default_factory=list) + plugins: list[str] = field(default_factory=list) + keyboards: list[str] = field(default_factory=list) errors: str = '' warnings: str = '' @@ -89,7 +89,7 @@ class MetadataSubsystem: plugs = _ba.app.plugins config_changed = False found_new = False - plugstates: Dict[str, Dict] = _ba.app.config.setdefault('Plugins', {}) + plugstates: dict[str, dict] = _ba.app.config.setdefault('Plugins', {}) assert isinstance(plugstates, dict) # Create a potential-plugin for each class we found in the scan. @@ -151,7 +151,7 @@ class MetadataSubsystem: 'timeout waiting for meta scan to complete.') return self.metascan - def get_game_types(self) -> List[Type[ba.GameActivity]]: + def get_game_types(self) -> list[type[ba.GameActivity]]: """Return available game types.""" from ba._general import getclass from ba._gameactivity import GameActivity @@ -167,11 +167,11 @@ class MetadataSubsystem: unowned = self.get_unowned_game_types() return [cls for cls in gameclasses if cls not in unowned] - def get_unowned_game_types(self) -> Set[Type[ba.GameActivity]]: + def get_unowned_game_types(self) -> set[type[ba.GameActivity]]: """Return present game types not owned by the current account.""" try: from ba import _store - unowned_games: Set[Type[ba.GameActivity]] = set() + unowned_games: set[type[ba.GameActivity]] = set() if not _ba.app.headless_mode: for section in _store.get_store_layout()['minigames']: for mname in section['items']: @@ -188,7 +188,7 @@ class MetadataSubsystem: class ScanThread(threading.Thread): """Thread to scan script dirs for metadata.""" - def __init__(self, dirs: List[str]): + def __init__(self, dirs: list[str]): super().__init__() self._dirs = dirs @@ -215,7 +215,7 @@ class ScanThread(threading.Thread): class DirectoryScan: """Handles scanning directories for metadata.""" - def __init__(self, paths: List[str]): + def __init__(self, paths: list[str]): """Given one or more paths, parses available meta information. It is assumed that these paths are also in PYTHONPATH. @@ -228,7 +228,7 @@ class DirectoryScan: def _get_path_module_entries( self, path: pathlib.Path, subpath: Union[str, pathlib.Path], - modules: List[Tuple[pathlib.Path, pathlib.Path]]) -> None: + modules: list[tuple[pathlib.Path, pathlib.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' @@ -254,7 +254,7 @@ class DirectoryScan: def scan(self) -> None: """Scan provided paths.""" - modules: List[Tuple[pathlib.Path, pathlib.Path]] = [] + modules: list[tuple[pathlib.Path, pathlib.Path]] = [] for path in self.paths: self._get_path_module_entries(path, '', modules) for moduledir, subpath in modules: @@ -278,7 +278,7 @@ class DirectoryScan: else: fpath = pathlib.Path(moduledir, subpath, '__init__.py') ispackage = True - with fpath.open() as infile: + with fpath.open(encoding='utf-8') as infile: flines = infile.readlines() meta_lines = { lnum: l[1:].split() @@ -305,7 +305,7 @@ class DirectoryScan: # If its a package, recurse into its subpackages. if ispackage: try: - submodules: List[Tuple[pathlib.Path, pathlib.Path]] = [] + submodules: list[tuple[pathlib.Path, pathlib.Path]] = [] self._get_path_module_entries(moduledir, subpath, submodules) for submodule in submodules: if submodule[1].name != '__init__.py': @@ -316,8 +316,8 @@ class DirectoryScan: f"Error scanning '{subpath}': {traceback.format_exc()}\n") def _process_module_meta_tags(self, subpath: pathlib.Path, - flines: List[str], - meta_lines: Dict[int, List[str]]) -> None: + 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 @@ -360,7 +360,7 @@ class DirectoryScan: ': unrecognized export type "' + exporttype + '" on line ' + str(lindex + 1) + '.\n') - def _get_export_class_name(self, subpath: pathlib.Path, lines: List[str], + def _get_export_class_name(self, subpath: pathlib.Path, lines: list[str], lindex: int) -> Optional[str]: """Given line num of an export tag, returns its operand class name.""" lindexorig = lindex @@ -387,7 +387,7 @@ class DirectoryScan: return classname def get_api_requirement(self, subpath: pathlib.Path, - meta_lines: Dict[int, List[str]], + meta_lines: dict[int, list[str]], toplevel: bool) -> Optional[int]: """Return an API requirement integer or None if none present. diff --git a/dist/ba_data/python/ba/_multiteamsession.py b/dist/ba_data/python/ba/_multiteamsession.py index 910b72e..683ddde 100644 --- a/dist/ba_data/python/ba/_multiteamsession.py +++ b/dist/ba_data/python/ba/_multiteamsession.py @@ -12,7 +12,7 @@ from ba._session import Session from ba._error import NotFoundError, print_error if TYPE_CHECKING: - from typing import Optional, Any, Dict, List, Type, Sequence + from typing import Optional, Any, Sequence import ba DEFAULT_TEAM_COLORS = ((0.1, 0.25, 1.0), (1.0, 0.25, 0.2)) @@ -105,9 +105,9 @@ class MultiTeamSession(Session): shuffle=self._playlist_randomize) # Get a game on deck ready to go. - self._current_game_spec: Optional[Dict[str, Any]] = None - self._next_game_spec: Dict[str, Any] = self._playlist.pull_next() - self._next_game: Type[ba.GameActivity] = ( + self._current_game_spec: Optional[dict[str, Any]] = None + self._next_game_spec: dict[str, Any] = self._playlist.pull_next() + self._next_game: type[ba.GameActivity] = ( self._next_game_spec['resolved_type']) # Go ahead and instantiate the next game we'll @@ -129,7 +129,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'] + gametype: type[GameActivity] = self._next_game_spec['resolved_type'] assert issubclass(gametype, GameActivity) return gametype.get_settings_display_string(self._next_game_spec) @@ -274,13 +274,13 @@ class ShuffleList: (avoids repeats in maps or game types) """ - def __init__(self, items: List[Dict[str, Any]], shuffle: bool = True): + def __init__(self, items: list[dict[str, Any]], shuffle: bool = True): self.source_list = items self.shuffle = shuffle - self.shuffle_list: List[Dict[str, Any]] = [] - self.last_gotten: Optional[Dict[str, Any]] = None + self.shuffle_list: list[dict[str, Any]] = [] + self.last_gotten: Optional[dict[str, Any]] = None - def pull_next(self) -> Dict[str, Any]: + def pull_next(self) -> dict[str, Any]: """Pull and return the next item on the shuffle-list.""" # Refill our list if its empty. diff --git a/dist/ba_data/python/ba/_music.py b/dist/ba_data/python/ba/_music.py index c2f92d9..7cf3815 100644 --- a/dist/ba_data/python/ba/_music.py +++ b/dist/ba_data/python/ba/_music.py @@ -11,7 +11,7 @@ from enum import Enum import _ba if TYPE_CHECKING: - from typing import Callable, Any, Optional, Dict, Union, Type + from typing import Callable, Any, Optional, Union import ba @@ -69,7 +69,7 @@ class AssetSoundtrackEntry: # What gets played by default for our different music types: -ASSET_SOUNDTRACK_ENTRIES: Dict[MusicType, AssetSoundtrackEntry] = { +ASSET_SOUNDTRACK_ENTRIES: dict[MusicType, AssetSoundtrackEntry] = { MusicType.MENU: AssetSoundtrackEntry('menuMusic'), MusicType.VICTORY: @@ -130,8 +130,8 @@ class MusicSubsystem: self._music_node: Optional[_ba.Node] = None self._music_mode: MusicPlayMode = MusicPlayMode.REGULAR self._music_player: Optional[MusicPlayer] = None - self._music_player_type: Optional[Type[MusicPlayer]] = None - self.music_types: Dict[MusicPlayMode, Optional[MusicType]] = { + self._music_player_type: Optional[type[MusicPlayer]] = None + self.music_types: dict[MusicPlayMode, Optional[MusicType]] = { MusicPlayMode.REGULAR: None, MusicPlayMode.TEST: None } @@ -273,7 +273,7 @@ class MusicSubsystem: musictype: Union[MusicType, str, None], continuous: bool = False, mode: MusicPlayMode = MusicPlayMode.REGULAR, - testsoundtrack: Dict[str, Any] = None) -> None: + testsoundtrack: dict[str, Any] = None) -> None: """Plays the requested music type/mode. For most cases, setmusic() is the proper call to use, which itself @@ -329,10 +329,10 @@ class MusicSubsystem: else: self._play_internal_music(musictype) - def _get_user_soundtrack(self) -> Dict[str, Any]: + def _get_user_soundtrack(self) -> dict[str, Any]: """Return current user soundtrack or empty dict otherwise.""" cfg = _ba.app.config - soundtrack: Dict[str, Any] = {} + soundtrack: dict[str, Any] = {} soundtrackname = cfg.get('Soundtrack') if soundtrackname is not None and soundtrackname != '__default__': try: diff --git a/dist/ba_data/python/ba/_net.py b/dist/ba_data/python/ba/_net.py index ea12d48..c27f3db 100644 --- a/dist/ba_data/python/ba/_net.py +++ b/dist/ba_data/python/ba/_net.py @@ -12,10 +12,10 @@ from typing import TYPE_CHECKING import _ba if TYPE_CHECKING: - from typing import Any, Dict, Union, Callable, Optional + from typing import Any, Union, Callable, Optional import socket import ba - MasterServerCallback = Callable[[Union[None, Dict[str, Any]]], None] + MasterServerCallback = Callable[[Union[None, dict[str, Any]]], None] # Timeout for standard functions talking to the master-server/etc. DEFAULT_REQUEST_TIMEOUT_SECONDS = 60 @@ -25,7 +25,7 @@ class NetworkSubsystem: """Network related app subsystem.""" def __init__(self) -> None: - self.region_pings: Dict[str, float] = {} + self.region_pings: dict[str, float] = {} def get_ip_address_type(addr: str) -> socket.AddressFamily: @@ -61,7 +61,7 @@ class MasterServerCallThread(threading.Thread): """Thread to communicate with the master-server.""" def __init__(self, request: str, request_type: str, - data: Optional[Dict[str, Any]], + data: Optional[dict[str, Any]], callback: Optional[MasterServerCallback], response_type: MasterServerResponseType): super().__init__() @@ -79,7 +79,7 @@ class MasterServerCallThread(threading.Thread): self._activity = weakref.ref( activity) if activity is not None else None - def _run_callback(self, arg: Union[None, Dict[str, Any]]) -> None: + def _run_callback(self, arg: Union[None, dict[str, Any]]) -> None: # If we were created in an activity context and that activity has # since died, do nothing. # FIXME: Should we just be using a ContextCall instead of doing @@ -102,7 +102,7 @@ class MasterServerCallThread(threading.Thread): import urllib.error import json - from efro.net import is_urllib_network_error + from efro.error import is_urllib_network_error from ba import _general try: self._data = _general.utf8_all(self._data) @@ -170,7 +170,7 @@ class MasterServerCallThread(threading.Thread): def master_server_get( request: str, - data: Dict[str, Any], + data: dict[str, Any], callback: Optional[MasterServerCallback] = None, response_type: MasterServerResponseType = MasterServerResponseType.JSON ) -> None: @@ -181,7 +181,7 @@ def master_server_get( def master_server_post( request: str, - data: Dict[str, Any], + data: dict[str, Any], callback: Optional[MasterServerCallback] = None, response_type: MasterServerResponseType = MasterServerResponseType.JSON ) -> None: diff --git a/dist/ba_data/python/ba/_player.py b/dist/ba_data/python/ba/_player.py index f0592a6..a472853 100644 --- a/dist/ba_data/python/ba/_player.py +++ b/dist/ba_data/python/ba/_player.py @@ -13,8 +13,7 @@ from ba._error import (SessionPlayerNotFoundError, print_exception, from ba._messages import DeathType, DieMessage if TYPE_CHECKING: - from typing import (Type, Optional, Sequence, Dict, Any, Union, Tuple, - Callable) + from typing import Optional, Sequence, Any, Union, Callable import ba PlayerType = TypeVar('PlayerType', bound='ba.Player') @@ -245,8 +244,8 @@ class Player(Generic[TeamType]): assert not self._expired return self.actor is not None and self.actor.is_alive() - def get_icon(self) -> Dict[str, Any]: - """get_icon() -> Dict[str, Any] + def get_icon(self) -> dict[str, Any]: + """get_icon() -> dict[str, Any] Returns the character's icon (images, colors, etc contained in a dict) """ @@ -254,7 +253,7 @@ class Player(Generic[TeamType]): assert not self._expired return self._sessionplayer.get_icon() - def assigninput(self, inputtype: Union[ba.InputType, Tuple[ba.InputType, + def assigninput(self, inputtype: Union[ba.InputType, tuple[ba.InputType, ...]], call: Callable) -> None: """assigninput(type: Union[ba.InputType, Tuple[ba.InputType, ...]], @@ -302,7 +301,7 @@ class EmptyPlayer(Player['ba.EmptyTeam']): # instead of requiring extra work by them. -def playercast(totype: Type[PlayerType], player: ba.Player) -> PlayerType: +def playercast(totype: type[PlayerType], player: ba.Player) -> PlayerType: """Cast a ba.Player to a specific ba.Player subclass. Category: Gameplay Functions @@ -320,7 +319,7 @@ 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], +def playercast_o(totype: type[PlayerType], player: Optional[ba.Player]) -> Optional[PlayerType]: """A variant of ba.playercast() for use with optional ba.Player values. diff --git a/dist/ba_data/python/ba/_playlist.py b/dist/ba_data/python/ba/_playlist.py index 1f31ee6..b6e3604 100644 --- a/dist/ba_data/python/ba/_playlist.py +++ b/dist/ba_data/python/ba/_playlist.py @@ -5,17 +5,17 @@ from __future__ import annotations import copy -from typing import Any, TYPE_CHECKING, Dict, List +from typing import Any, TYPE_CHECKING if TYPE_CHECKING: - from typing import Type, Sequence + from typing import Sequence from ba import _session -PlaylistType = List[Dict[str, Any]] +PlaylistType = list[dict[str, Any]] def filter_playlist(playlist: PlaylistType, - sessiontype: Type[_session.Session], + sessiontype: type[_session.Session], add_resolved_type: bool = False, remove_unowned: bool = True, mark_unowned: bool = False) -> PlaylistType: @@ -32,7 +32,7 @@ def filter_playlist(playlist: PlaylistType, from ba import _map from ba import _general from ba import _gameactivity - goodlist: List[Dict] = [] + goodlist: list[dict] = [] unowned_maps: Sequence[str] if remove_unowned or mark_unowned: unowned_maps = _map.get_unowned_maps() diff --git a/dist/ba_data/python/ba/_plugin.py b/dist/ba_data/python/ba/_plugin.py index 025f7a2..7637717 100644 --- a/dist/ba_data/python/ba/_plugin.py +++ b/dist/ba_data/python/ba/_plugin.py @@ -10,7 +10,6 @@ from dataclasses import dataclass import _ba if TYPE_CHECKING: - from typing import List, Dict import ba @@ -23,8 +22,8 @@ class PluginSubsystem: """ def __init__(self) -> None: - self.potential_plugins: List[ba.PotentialPlugin] = [] - self.active_plugins: Dict[str, ba.Plugin] = {} + self.potential_plugins: list[ba.PotentialPlugin] = [] + self.active_plugins: dict[str, ba.Plugin] = {} def on_app_launch(self) -> None: """Should be called at app launch time.""" @@ -73,9 +72,9 @@ class PluginSubsystem: # plugins, but that is only used to give the user a list of plugins # that they can enable. (we wouldn't want to look at meta-scan here # anyway because it may not be done yet at this point in the launch) - plugstates: Dict[str, Dict] = _ba.app.config.get('Plugins', {}) + plugstates: dict[str, dict] = _ba.app.config.get('Plugins', {}) assert isinstance(plugstates, dict) - plugkeys: List[str] = sorted(key for key, val in plugstates.items() + plugkeys: list[str] = sorted(key for key, val in plugstates.items() if val.get('enabled', False)) for plugkey in plugkeys: try: diff --git a/dist/ba_data/python/ba/_powerup.py b/dist/ba_data/python/ba/_powerup.py index 7dd895c..520d302 100644 --- a/dist/ba_data/python/ba/_powerup.py +++ b/dist/ba_data/python/ba/_powerup.py @@ -8,7 +8,7 @@ from typing import TYPE_CHECKING from dataclasses import dataclass if TYPE_CHECKING: - from typing import Sequence, Tuple, Optional + from typing import Sequence, Optional import ba @@ -47,7 +47,7 @@ class PowerupAcceptMessage: """ -def get_default_powerup_distribution() -> Sequence[Tuple[str, int]]: +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), diff --git a/dist/ba_data/python/ba/_profile.py b/dist/ba_data/python/ba/_profile.py index a87e202..9f89a7f 100644 --- a/dist/ba_data/python/ba/_profile.py +++ b/dist/ba_data/python/ba/_profile.py @@ -9,7 +9,7 @@ from typing import TYPE_CHECKING import _ba if TYPE_CHECKING: - from typing import List, Tuple, Any, Dict, Optional + from typing import Any, Optional # NOTE: player color options are enforced server-side for non-pro accounts # so don't change these or they won't stick... @@ -20,7 +20,7 @@ PLAYER_COLORS = [(1, 0.15, 0.15), (0.2, 1, 0.2), (0.1, 0.1, 1), (0.2, 1, 1), (0.5, 0.5, 0.5), (1, 1, 1)] -def get_player_colors() -> List[Tuple[float, float, float]]: +def get_player_colors() -> list[tuple[float, float, float]]: """Return user-selectable player colors.""" return PLAYER_COLORS @@ -30,7 +30,7 @@ def get_player_profile_icon(profilename: str) -> str: (non-account profiles only) """ - from ba._enums import SpecialChar + from ba._generated.enums import SpecialChar appconfig = _ba.app.config icon: str @@ -50,8 +50,8 @@ def get_player_profile_icon(profilename: str) -> str: def get_player_profile_colors( profilename: Optional[str], - profiles: Dict[str, Dict[str, Any]] = None -) -> Tuple[Tuple[float, float, float], Tuple[float, float, float]]: + profiles: dict[str, dict[str, Any]] = None +) -> tuple[tuple[float, float, float], tuple[float, float, float]]: """Given a profile, return colors for them.""" appconfig = _ba.app.config if profiles is None: diff --git a/dist/ba_data/python/ba/_servermode.py b/dist/ba_data/python/ba/_servermode.py index fab3915..6df38d7 100644 --- a/dist/ba_data/python/ba/_servermode.py +++ b/dist/ba_data/python/ba/_servermode.py @@ -13,12 +13,13 @@ from bacommon.servermanager import (ServerCommand, StartServerModeCommand, ChatMessageCommand, ScreenMessageCommand, ClientListCommand, KickCommand) import _ba -from ba._enums import TimeType +from ba._generated.enums import TimeType from ba._freeforallsession import FreeForAllSession from ba._dualteamsession import DualTeamSession +from ba._coopsession import CoopSession if TYPE_CHECKING: - from typing import Optional, Dict, Any, Type + from typing import Optional, Any import ba from bacommon.servermanager import ServerConfig @@ -197,7 +198,7 @@ class ServerController: callback=self._access_check_response, ) - def _access_check_response(self, data: Optional[Dict[str, Any]]) -> None: + def _access_check_response(self, data: Optional[dict[str, Any]]) -> None: import os if data is None: print('error on UDP port access check (internet down?)') @@ -266,7 +267,7 @@ class ServerController: def _on_playlist_fetch_response( self, - result: Optional[Dict[str, Any]], + result: Optional[dict[str, Any]], ) -> None: if result is None: print('Error fetching playlist; aborting.') @@ -282,18 +283,21 @@ class ServerController: self._config.session_type = typename self._playlist_name = (result['playlistName']) - def _get_session_type(self) -> Type[ba.Session]: + def _get_session_type(self) -> type[ba.Session]: # Convert string session type to the class. # Hmm should we just keep this as a string? if self._config.session_type == 'ffa': return FreeForAllSession if self._config.session_type == 'teams': return DualTeamSession + if self._config.session_type == 'coop': + return CoopSession raise RuntimeError( 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.""" + # pylint: disable=too-many-branches app = _ba.app appcfg = app.config sessiontype = self._get_session_type() @@ -311,6 +315,8 @@ class ServerController: ptypename = 'Free-for-All' elif sessiontype is DualTeamSession: ptypename = 'Team Tournament' + elif sessiontype is CoopSession: + ptypename = 'Coop' else: raise RuntimeError(f'Unknown session type {sessiontype}') @@ -340,6 +346,11 @@ class ServerController: appcfg['Team Tournament Playlist Selection'] = self._playlist_name appcfg['Team Tournament Playlist Randomize'] = ( self._config.playlist_shuffle) + elif sessiontype is CoopSession: + app.coop_session_args = { + 'campaign': self._config.coop_campaign, + 'level': self._config.coop_level, + } else: raise RuntimeError(f'Unknown session type {sessiontype}') diff --git a/dist/ba_data/python/ba/_session.py b/dist/ba_data/python/ba/_session.py index 44e9281..0b59fed 100644 --- a/dist/ba_data/python/ba/_session.py +++ b/dist/ba_data/python/ba/_session.py @@ -12,7 +12,7 @@ from ba._language import Lstr from ba._player import Player if TYPE_CHECKING: - from typing import Sequence, List, Dict, Any, Optional, Set + from typing import Sequence, Any, Optional import ba @@ -63,10 +63,6 @@ class Session: team instead of their own profile colors. This only applies if use_teams is enabled. - allow_mid_activity_joins - Whether players should be allowed to join in the middle of - activities. - customdata A shared dictionary for objects to use as storage on this session. Ensure that keys here are unique to avoid collisions. @@ -74,16 +70,15 @@ class Session: """ use_teams: bool = False use_team_colors: bool = True - allow_mid_activity_joins: bool = True # Note: even though these are instance vars, we annotate them at the # class level so that docs generation can access their types. lobby: ba.Lobby max_players: int min_players: int - sessionplayers: List[ba.SessionPlayer] + sessionplayers: list[ba.SessionPlayer] customdata: dict - sessionteams: List[ba.SessionTeam] + sessionteams: list[ba.SessionTeam] def __init__(self, depsets: Sequence[ba.DependencySet], @@ -113,7 +108,7 @@ class Session: # If things are missing, we'll try to gather them into a single # missing-deps exception if possible to give the caller a clean # path to download missing stuff and try again. - missing_asset_packages: Set[str] = set() + missing_asset_packages: set[str] = set() for depset in depsets: try: depset.resolve() @@ -138,7 +133,7 @@ class Session: # Ok; looks like our dependencies check out. # Now give the engine a list of asset-set-ids to pass along to clients. - required_asset_packages: Set[str] = set() + required_asset_packages: set[str] = set() for depset in depsets: required_asset_packages.update(depset.get_asset_package_ids()) @@ -210,18 +205,26 @@ class Session: raise NodeNotFoundError() return node + def should_allow_mid_activity_joins(self, activity: ba.Activity) -> bool: + """Ask ourself if we should allow joins during an Activity. + + Note that for a join to be allowed, both the Session and Activity + have to be ok with it (via this function and the + Activity.allow_mid_activity_joins property. + """ + del activity # Unused. + return True + def on_player_request(self, player: ba.SessionPlayer) -> bool: """Called when a new ba.Player wants to join the Session. This should return True or False to accept/reject. """ - from tools import whitelist - whitelist.handle_player_request(player) + # Limit player counts *unless* we're in a stress test. if _ba.app.stress_test_reset_timer is None: if len(self.sessionplayers) >= self.max_players: - # Print a rejection message *only* to the client trying to # join (prevents spamming everyone else in the game). _ba.playsound(_ba.getsound('error')) @@ -278,7 +281,7 @@ class Session: assert isinstance(player, (Player, type(None))) # Remove them from any current Activity. - if activity is not None: + if player is not None and activity is not None: if player in activity.players: activity.remove_player(sessionplayer) else: @@ -335,7 +338,7 @@ class Session: def _launch_end_session_activity(self) -> None: """(internal)""" from ba._activitytypes import EndSessionActivity - from ba._enums import TimeType + from ba._generated.enums import TimeType with _ba.Context(self): curtime = _ba.time(TimeType.REAL) if self._ending: @@ -368,7 +371,7 @@ class Session: will replace the old. """ from ba._general import Call - from ba._enums import TimeType + from ba._generated.enums import TimeType # Only pay attention if this is coming from our current activity. if activity is not self._activity_retained: @@ -432,7 +435,7 @@ class Session: (on_transition_in, etc) to get it. (so you can't do session.setactivity(foo) and then ba.newnode() to add a node to foo) """ - from ba._enums import TimeType + from ba._generated.enums import TimeType # Make sure we don't get called recursively. _rlock = self._SetActivityScopedLock(self) @@ -492,7 +495,7 @@ class Session: """Return the current foreground activity for this session.""" return self._activity_weak() - def get_custom_menu_entries(self) -> List[Dict[str, Any]]: + def get_custom_menu_entries(self) -> list[dict[str, Any]]: """Subclasses can override this to provide custom menu entries. The returned value should be a list of dicts, each containing @@ -658,7 +661,8 @@ class Session: # 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 self.allow_mid_activity_joins: + 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( diff --git a/dist/ba_data/python/ba/_settings.py b/dist/ba_data/python/ba/_settings.py index 1b3e404..cf590b5 100644 --- a/dist/ba_data/python/ba/_settings.py +++ b/dist/ba_data/python/ba/_settings.py @@ -8,7 +8,7 @@ from typing import TYPE_CHECKING from dataclasses import dataclass if TYPE_CHECKING: - from typing import Any, List, Tuple + from typing import Any @dataclass @@ -61,7 +61,7 @@ class ChoiceSetting(Setting): Category: Settings Classes """ - choices: List[Tuple[str, Any]] + choices: list[tuple[str, Any]] @dataclass @@ -71,7 +71,7 @@ class IntChoiceSetting(ChoiceSetting): Category: Settings Classes """ default: int - choices: List[Tuple[str, int]] + choices: list[tuple[str, int]] @dataclass @@ -81,4 +81,4 @@ class FloatChoiceSetting(ChoiceSetting): Category: Settings Classes """ default: float - choices: List[Tuple[str, 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 bf3becb..486a39e 100644 --- a/dist/ba_data/python/ba/_stats.py +++ b/dist/ba_data/python/ba/_stats.py @@ -14,8 +14,7 @@ from ba._error import (print_exception, print_error, SessionTeamNotFoundError, if TYPE_CHECKING: import ba - from weakref import ReferenceType - from typing import Any, Dict, Optional, Sequence, Union, Tuple + from typing import Any, Optional, Sequence, Union @dataclass @@ -58,7 +57,7 @@ class PlayerRecord: self._stats = weakref.ref(stats) self._last_sessionplayer: Optional[ba.SessionPlayer] = None self._sessionplayer: Optional[ba.SessionPlayer] = None - self._sessionteam: Optional[ReferenceType[ba.SessionTeam]] = None + self._sessionteam: Optional[weakref.ref[ba.SessionTeam]] = None self.streak = 0 self.associate_with_sessionplayer(sessionplayer) @@ -90,7 +89,7 @@ class PlayerRecord: """Return the player entry's name.""" return self.name_full if full else self.name - def get_icon(self) -> Dict[str, Any]: + def get_icon(self) -> dict[str, Any]: """Get the icon for this instance's player.""" player = self._last_sessionplayer assert player is not None @@ -181,7 +180,7 @@ class PlayerRecord: sound = stats.orchestrahitsound4 def _apply(name2: Lstr, score2: int, showpoints2: bool, - color2: Tuple[float, float, float, float], scale2: float, + color2: tuple[float, float, float, float], scale2: float, sound2: Optional[ba.Sound]) -> None: from bastd.actor.popuptext import PopupText @@ -237,8 +236,8 @@ class Stats: """ def __init__(self) -> None: - self._activity: Optional[ReferenceType[ba.Activity]] = None - self._player_records: Dict[str, PlayerRecord] = {} + self._activity: Optional[weakref.ref[ba.Activity]] = None + self._player_records: dict[str, PlayerRecord] = {} self.orchestrahitsound1: Optional[ba.Sound] = None self.orchestrahitsound2: Optional[ba.Sound] = None self.orchestrahitsound3: Optional[ba.Sound] = None @@ -303,7 +302,7 @@ class Stats: self._player_records[name] = PlayerRecord(name, name_full, player, self) - def get_records(self) -> Dict[str, ba.PlayerRecord]: + def get_records(self) -> dict[str, ba.PlayerRecord]: """Get PlayerRecord corresponding to still-existing players.""" records = {} diff --git a/dist/ba_data/python/ba/_store.py b/dist/ba_data/python/ba/_store.py index fd4e5bf..0dc91f9 100644 --- a/dist/ba_data/python/ba/_store.py +++ b/dist/ba_data/python/ba/_store.py @@ -9,11 +9,11 @@ from typing import TYPE_CHECKING import _ba if TYPE_CHECKING: - from typing import Type, List, Dict, Tuple, Optional, Any + from typing import Optional, Any import ba -def get_store_item(item: str) -> Dict[str, Any]: +def get_store_item(item: str) -> dict[str, Any]: """(internal)""" return get_store_items()[item] @@ -32,17 +32,17 @@ def get_store_item_name_translated(item_name: str) -> ba.Lstr: subs=[('${APP_NAME}', _language.Lstr(resource='titleText'))]) if item_name.startswith('maps.'): - map_type: Type[ba.Map] = item_info['map_type'] + map_type: type[ba.Map] = item_info['map_type'] return _map.get_map_display_string(map_type.name) if item_name.startswith('games.'): - gametype: Type[ba.GameActivity] = item_info['gametype'] + gametype: type[ba.GameActivity] = item_info['gametype'] return gametype.get_display_string() if item_name.startswith('icons.'): return _language.Lstr(resource='editProfileWindow.iconText') raise ValueError('unrecognized item: ' + item_name) -def get_store_item_display_size(item_name: str) -> Tuple[float, float]: +def get_store_item_display_size(item_name: str) -> tuple[float, float]: """(internal)""" if item_name.startswith('characters.'): return 340 * 0.6, 430 * 0.6 @@ -55,13 +55,13 @@ def get_store_item_display_size(item_name: str) -> Tuple[float, float]: return 450 * 0.6, 450 * 0.6 -def get_store_items() -> Dict[str, Dict]: +def get_store_items() -> dict[str, dict]: """Returns info about purchasable items. (internal) """ # pylint: disable=cyclic-import - from ba._enums import SpecialChar + from ba._generated.enums import SpecialChar from bastd import maps if _ba.app.store_items is None: from bastd.game import ninjafight @@ -285,7 +285,7 @@ def get_store_items() -> Dict[str, Dict]: return store_items -def get_store_layout() -> Dict[str, List[Dict[str, Any]]]: +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.""" @@ -421,7 +421,7 @@ def get_available_purchase_count(tab: str = None) -> int: return 0 -def _calc_count_for_tab(tabval: List[Dict[str, Any]], our_tickets: 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']: @@ -440,9 +440,9 @@ def get_available_sale_time(tab: str) -> Optional[int]: # pylint: disable=too-many-locals try: import datetime - from ba._enums import TimeType, TimeFormat + from ba._generated.enums import TimeType, TimeFormat app = _ba.app - sale_times: List[Optional[int]] = [] + sale_times: list[Optional[int]] = [] # Calc time for our pro sale (old special case). if tab == 'extras': diff --git a/dist/ba_data/python/ba/_team.py b/dist/ba_data/python/ba/_team.py index 67e254b..0cdfbe7 100644 --- a/dist/ba_data/python/ba/_team.py +++ b/dist/ba_data/python/ba/_team.py @@ -10,8 +10,7 @@ from typing import TYPE_CHECKING, TypeVar, Generic from ba._error import print_exception if TYPE_CHECKING: - from weakref import ReferenceType - from typing import Dict, List, Sequence, Tuple, Union, Optional + from typing import Sequence, Union, Optional import ba @@ -47,8 +46,8 @@ class SessionTeam: # Annotate our attr types at the class level so they're introspectable. name: Union[ba.Lstr, str] - color: Tuple[float, ...] # FIXME: can't we make this fixed len? - players: List[ba.SessionPlayer] + color: tuple[float, ...] # FIXME: can't we make this fixed len? + players: list[ba.SessionPlayer] customdata: dict id: int @@ -88,11 +87,11 @@ class Team(Generic[PlayerType]): # Defining these types at the class level instead of in __init__ so # that types are introspectable (these are still instance attrs). - players: List[PlayerType] + players: list[PlayerType] id: int name: Union[ba.Lstr, str] - color: Tuple[float, ...] # FIXME: can't we make this fixed length? - _sessionteam: ReferenceType[SessionTeam] + color: tuple[float, ...] # FIXME: can't we make this fixed length? + _sessionteam: weakref.ref[SessionTeam] _expired: bool _postinited: bool _customdata: dict @@ -127,7 +126,7 @@ class Team(Generic[PlayerType]): self._postinited = True def manual_init(self, team_id: int, name: Union[ba.Lstr, str], - color: Tuple[float, ...]) -> None: + color: tuple[float, ...]) -> None: """Manually init a team for uses such as bots.""" self.id = team_id self.name = name diff --git a/dist/ba_data/python/ba/_teamgame.py b/dist/ba_data/python/ba/_teamgame.py index afa8519..9307621 100644 --- a/dist/ba_data/python/ba/_teamgame.py +++ b/dist/ba_data/python/ba/_teamgame.py @@ -13,7 +13,7 @@ from ba._gameresults import GameResults from ba._dualteamsession import DualTeamSession if TYPE_CHECKING: - from typing import Any, Dict, Type, Sequence + from typing import Any, Sequence from bastd.actor.playerspaz import PlayerSpaz import ba @@ -31,7 +31,7 @@ class TeamGameActivity(GameActivity[PlayerType, TeamType]): """ @classmethod - def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool: + def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool: """ Class method override; returns True for ba.DualTeamSessions and ba.FreeForAllSessions; diff --git a/dist/ba_data/python/ba/_tips.py b/dist/ba_data/python/ba/_tips.py index 7a2501d..c6e8955 100644 --- a/dist/ba_data/python/ba/_tips.py +++ b/dist/ba_data/python/ba/_tips.py @@ -3,12 +3,16 @@ """Functionality related to game tips. These can be shown at opportune times such as between rounds.""" +from __future__ import annotations +from typing import TYPE_CHECKING import random -from typing import List import _ba +if TYPE_CHECKING: + pass + def get_next_tip() -> str: """Returns the next tip to be displayed.""" @@ -20,7 +24,7 @@ def get_next_tip() -> str: return tip -def get_all_tips() -> List[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}\' ' diff --git a/dist/ba_data/python/ba/_tournament.py b/dist/ba_data/python/ba/_tournament.py index bb1cc1e..ec1618a 100644 --- a/dist/ba_data/python/ba/_tournament.py +++ b/dist/ba_data/python/ba/_tournament.py @@ -9,13 +9,13 @@ from typing import TYPE_CHECKING import _ba if TYPE_CHECKING: - from typing import Dict, List, Any + from typing import Any -def get_tournament_prize_strings(entry: Dict[str, Any]) -> List[str]: +def get_tournament_prize_strings(entry: dict[str, Any]) -> list[str]: """Given a tournament entry, return strings for its prize levels.""" # pylint: disable=too-many-locals - from ba._enums import SpecialChar + from ba._generated.enums import SpecialChar from ba._gameutils import get_trophy_string range1 = entry.get('prizeRange1') range2 = entry.get('prizeRange2') diff --git a/dist/ba_data/python/ba/_ui.py b/dist/ba_data/python/ba/_ui.py index c5af590..bf01947 100644 --- a/dist/ba_data/python/ba/_ui.py +++ b/dist/ba_data/python/ba/_ui.py @@ -7,10 +7,10 @@ from __future__ import annotations from typing import TYPE_CHECKING import _ba -from ba._enums import UIScale +from ba._generated.enums import UIScale if TYPE_CHECKING: - from typing import Optional, Dict, Any, Callable, List, Type + from typing import Optional, Any, Callable from ba.ui import UICleanupCheck import ba @@ -43,13 +43,13 @@ class UISubsystem: else: raise RuntimeError(f'Invalid UIScale value: {interfacetype}') - self.window_states: Dict[Type, Any] = {} # FIXME: Kill this. + self.window_states: dict[type, Any] = {} # FIXME: Kill this. self.main_menu_selection: Optional[str] = None # FIXME: Kill this. self.have_party_queue_window = False self.quit_window: Any = None self.dismiss_wii_remotes_window_call: (Optional[Callable[[], Any]]) = None - self.cleanupchecks: List[UICleanupCheck] = [] + self.cleanupchecks: list[UICleanupCheck] = [] self.upkeeptimer: Optional[ba.Timer] = None self.use_toolbars = env.get('toolbar_test', True) self.party_window: Any = None # FIXME: Don't use Any. @@ -70,7 +70,7 @@ class UISubsystem: def on_app_launch(self) -> None: """Should be run on app launch.""" from ba.ui import UIController, ui_upkeep - from ba._enums import TimeType + from ba._generated.enums import TimeType # IMPORTANT: If tweaking UI stuff, make sure it behaves for small, # medium, and large UI modes. (doesn't run off screen, etc). @@ -107,7 +107,7 @@ class UISubsystem: def set_main_menu_window(self, window: ba.Widget) -> None: """Set the current 'main' window, replacing any existing.""" existing = self._main_menu_window - from ba._enums import TimeType + from ba._generated.enums import TimeType from inspect import currentframe, getframeinfo # Let's grab the location where we were called from to report diff --git a/dist/ba_data/python/ba/macmusicapp.py b/dist/ba_data/python/ba/macmusicapp.py index c8a64d7..598f572 100644 --- a/dist/ba_data/python/ba/macmusicapp.py +++ b/dist/ba_data/python/ba/macmusicapp.py @@ -10,7 +10,7 @@ import _ba from ba._music import MusicPlayer if TYPE_CHECKING: - from typing import List, Optional, Callable, Any + from typing import Optional, Callable, Any class MacMusicAppMusicPlayer(MusicPlayer): @@ -60,7 +60,7 @@ class _MacMusicAppThread(threading.Thread): def __init__(self) -> None: super().__init__() self._commands_available = threading.Event() - self._commands: List[List] = [] + self._commands: list[list] = [] self._volume = 1.0 self._current_playlist: Optional[str] = None self._orig_volume: Optional[int] = None @@ -69,7 +69,7 @@ class _MacMusicAppThread(threading.Thread): """Run the Music.app thread.""" from ba._general import Call from ba._language import Lstr - from ba._enums import TimeType + from ba._generated.enums import TimeType _ba.set_thread_name('BA_MacMusicAppThread') _ba.mac_music_app_init() @@ -153,7 +153,7 @@ 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() diff --git a/dist/ba_data/python/ba/modutils.py b/dist/ba_data/python/ba/modutils.py index 904711f..5984f25 100644 --- a/dist/ba_data/python/ba/modutils.py +++ b/dist/ba_data/python/ba/modutils.py @@ -9,7 +9,7 @@ import os import _ba if TYPE_CHECKING: - from typing import Optional, List, Sequence + from typing import Optional, Sequence def get_human_readable_user_scripts_path() -> str: @@ -40,7 +40,7 @@ def get_human_readable_user_scripts_path() -> str: def _request_storage_permission() -> bool: """If needed, requests storage permission from the user (& return true).""" from ba._language import Lstr - from ba._enums import Permission + from ba._generated.enums import Permission if not _ba.have_permission(Permission.STORAGE): _ba.playsound(_ba.getsound('error')) _ba.screenmessage(Lstr(resource='storagePermissionAccessText'), @@ -73,7 +73,7 @@ def show_user_scripts() -> None: usd: Optional[str] = app.python_directory_user if usd is not None and os.path.isdir(usd): file_name = usd + '/about_this_folder.txt' - with open(file_name, 'w') as outfile: + 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.') diff --git a/dist/ba_data/python/ba/osmusic.py b/dist/ba_data/python/ba/osmusic.py index 5257e0a..64f2bd1 100644 --- a/dist/ba_data/python/ba/osmusic.py +++ b/dist/ba_data/python/ba/osmusic.py @@ -12,7 +12,7 @@ import _ba from ba._music import MusicPlayer if TYPE_CHECKING: - from typing import Callable, Any, Union, List, Optional + from typing import Callable, Any, Union, Optional class OSMusicPlayer(MusicPlayer): @@ -26,7 +26,7 @@ class OSMusicPlayer(MusicPlayer): self._actually_playing = False @classmethod - def get_valid_music_file_extensions(cls) -> List[str]: + def get_valid_music_file_extensions(cls) -> list[str]: """Return file extensions for types playable on this device.""" # FIXME: should ask the C++ layer for these; just hard-coding for now. return ['mp3', 'ogg', 'm4a', 'wav', 'flac', 'mid'] @@ -60,7 +60,7 @@ class OSMusicPlayer(MusicPlayer): self._on_play_folder_cb).start() def _on_play_folder_cb(self, - result: Union[str, List[str]], + result: Union[str, list[str]], error: Optional[str] = None) -> None: from ba import _language if error is not None: @@ -94,8 +94,8 @@ class OSMusicPlayer(MusicPlayer): class _PickFolderSongThread(threading.Thread): - def __init__(self, path: str, valid_extensions: List[str], - callback: Callable[[Union[str, List[str]], Optional[str]], + def __init__(self, path: str, valid_extensions: list[str], + callback: Callable[[Union[str, list[str]], Optional[str]], None]): super().__init__() self._valid_extensions = valid_extensions @@ -108,7 +108,7 @@ class _PickFolderSongThread(threading.Thread): do_print_error = True try: _ba.set_thread_name('BA_PickFolderSongThread') - all_files: List[str] = [] + all_files: list[str] = [] valid_extensions = ['.' + x for x in self._valid_extensions] for root, _subdirs, filenames in os.walk(self._path): for fname in filenames: diff --git a/dist/ba_data/python/ba/ui/__init__.py b/dist/ba_data/python/ba/ui/__init__.py index e65453f..a3b16de 100644 --- a/dist/ba_data/python/ba/ui/__init__.py +++ b/dist/ba_data/python/ba/ui/__init__.py @@ -10,12 +10,11 @@ from dataclasses import dataclass from typing import TYPE_CHECKING, cast, Type import _ba -from ba._enums import TimeType +from ba._generated.enums import TimeType from ba._general import print_active_refs if TYPE_CHECKING: - from typing import Optional, List, Any - from weakref import ReferenceType + from typing import Optional, Any import ba @@ -45,7 +44,7 @@ class Window: @dataclass class UICleanupCheck: """Holds info about a uicleanupcheck target.""" - obj: ReferenceType + obj: weakref.ref widget: ba.Widget widget_death_time: Optional[float] @@ -126,15 +125,15 @@ class UIController: def __init__(self) -> None: # FIXME: document why we have separate stacks for game and menu... - self._main_stack_game: List[UIEntry] = [] - self._main_stack_menu: List[UIEntry] = [] + self._main_stack_game: list[UIEntry] = [] + self._main_stack_menu: list[UIEntry] = [] # This points at either the game or menu stack. - self._main_stack: Optional[List[UIEntry]] = None + self._main_stack: Optional[list[UIEntry]] = None # There's only one of these since we don't need to preserve its state # between sessions. - self._dialog_stack: List[UIEntry] = [] + self._dialog_stack: list[UIEntry] = [] def show_main_menu(self, in_game: bool = True) -> None: """Show the main menu, clearing other UIs from location stacks.""" diff --git a/dist/ba_data/python/ba/ui/__pycache__/__init__.cpython-38.opt-1.pyc b/dist/ba_data/python/ba/ui/__pycache__/__init__.cpython-38.opt-1.pyc index 23ad2d3..3a394a5 100644 Binary files a/dist/ba_data/python/ba/ui/__pycache__/__init__.cpython-38.opt-1.pyc and b/dist/ba_data/python/ba/ui/__pycache__/__init__.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/ui/__pycache__/__init__.cpython-38.pyc b/dist/ba_data/python/ba/ui/__pycache__/__init__.cpython-38.pyc index 8d6b09a..cf3f8ad 100644 Binary files a/dist/ba_data/python/ba/ui/__pycache__/__init__.cpython-38.pyc and b/dist/ba_data/python/ba/ui/__pycache__/__init__.cpython-38.pyc differ diff --git a/dist/ba_data/python/ba/ui/__pycache__/__init__.cpython-39.opt-1.pyc b/dist/ba_data/python/ba/ui/__pycache__/__init__.cpython-39.opt-1.pyc new file mode 100644 index 0000000..9bc4190 Binary files /dev/null and b/dist/ba_data/python/ba/ui/__pycache__/__init__.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/ba/ui/__pycache__/__init__.cpython-39.pyc b/dist/ba_data/python/ba/ui/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..0ff11be Binary files /dev/null and b/dist/ba_data/python/ba/ui/__pycache__/__init__.cpython-39.pyc differ diff --git a/dist/ba_data/python/bacommon/__pycache__/__init__.cpython-38.pyc b/dist/ba_data/python/bacommon/__pycache__/__init__.cpython-38.pyc index 8b21d41..e4a37c6 100644 Binary files a/dist/ba_data/python/bacommon/__pycache__/__init__.cpython-38.pyc and b/dist/ba_data/python/bacommon/__pycache__/__init__.cpython-38.pyc differ diff --git a/dist/ba_data/python/bacommon/__pycache__/__init__.cpython-39.opt-1.pyc b/dist/ba_data/python/bacommon/__pycache__/__init__.cpython-39.opt-1.pyc new file mode 100644 index 0000000..4d59141 Binary files /dev/null and b/dist/ba_data/python/bacommon/__pycache__/__init__.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bacommon/__pycache__/__init__.cpython-39.pyc b/dist/ba_data/python/bacommon/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..2e2f91e Binary files /dev/null and b/dist/ba_data/python/bacommon/__pycache__/__init__.cpython-39.pyc differ diff --git a/dist/ba_data/python/bacommon/__pycache__/assets.cpython-38.opt-1.pyc b/dist/ba_data/python/bacommon/__pycache__/assets.cpython-38.opt-1.pyc index 12b7eaf..837cf2c 100644 Binary files a/dist/ba_data/python/bacommon/__pycache__/assets.cpython-38.opt-1.pyc and b/dist/ba_data/python/bacommon/__pycache__/assets.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python/bacommon/__pycache__/assets.cpython-39.opt-1.pyc b/dist/ba_data/python/bacommon/__pycache__/assets.cpython-39.opt-1.pyc new file mode 100644 index 0000000..1c4a894 Binary files /dev/null and b/dist/ba_data/python/bacommon/__pycache__/assets.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bacommon/__pycache__/net.cpython-38.opt-1.pyc b/dist/ba_data/python/bacommon/__pycache__/net.cpython-38.opt-1.pyc index bbcbfb7..e1c5d5c 100644 Binary files a/dist/ba_data/python/bacommon/__pycache__/net.cpython-38.opt-1.pyc and b/dist/ba_data/python/bacommon/__pycache__/net.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python/bacommon/__pycache__/net.cpython-38.pyc b/dist/ba_data/python/bacommon/__pycache__/net.cpython-38.pyc new file mode 100644 index 0000000..31bb0a1 Binary files /dev/null and b/dist/ba_data/python/bacommon/__pycache__/net.cpython-38.pyc differ diff --git a/dist/ba_data/python/bacommon/__pycache__/net.cpython-39.opt-1.pyc b/dist/ba_data/python/bacommon/__pycache__/net.cpython-39.opt-1.pyc new file mode 100644 index 0000000..ae7df66 Binary files /dev/null and b/dist/ba_data/python/bacommon/__pycache__/net.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bacommon/__pycache__/net.cpython-39.pyc b/dist/ba_data/python/bacommon/__pycache__/net.cpython-39.pyc new file mode 100644 index 0000000..7167a82 Binary files /dev/null and b/dist/ba_data/python/bacommon/__pycache__/net.cpython-39.pyc differ diff --git a/dist/ba_data/python/bacommon/__pycache__/servermanager.cpython-38.opt-1.pyc b/dist/ba_data/python/bacommon/__pycache__/servermanager.cpython-38.opt-1.pyc index 0f1a6da..aedd2d1 100644 Binary files a/dist/ba_data/python/bacommon/__pycache__/servermanager.cpython-38.opt-1.pyc and b/dist/ba_data/python/bacommon/__pycache__/servermanager.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python/bacommon/__pycache__/servermanager.cpython-38.pyc b/dist/ba_data/python/bacommon/__pycache__/servermanager.cpython-38.pyc index 4d06e37..197080f 100644 Binary files a/dist/ba_data/python/bacommon/__pycache__/servermanager.cpython-38.pyc and b/dist/ba_data/python/bacommon/__pycache__/servermanager.cpython-38.pyc differ diff --git a/dist/ba_data/python/bacommon/__pycache__/servermanager.cpython-39.opt-1.pyc b/dist/ba_data/python/bacommon/__pycache__/servermanager.cpython-39.opt-1.pyc new file mode 100644 index 0000000..19382d2 Binary files /dev/null and b/dist/ba_data/python/bacommon/__pycache__/servermanager.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bacommon/__pycache__/servermanager.cpython-39.pyc b/dist/ba_data/python/bacommon/__pycache__/servermanager.cpython-39.pyc new file mode 100644 index 0000000..b54937c Binary files /dev/null and b/dist/ba_data/python/bacommon/__pycache__/servermanager.cpython-39.pyc differ diff --git a/dist/ba_data/python/bacommon/assets.py b/dist/ba_data/python/bacommon/assets.py index 60d1409..311852d 100644 --- a/dist/ba_data/python/bacommon/assets.py +++ b/dist/ba_data/python/bacommon/assets.py @@ -4,10 +4,11 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from dataclasses import dataclass, field +from typing import TYPE_CHECKING, Optional, Annotated from enum import Enum -from efro import entity +from efro.dataclassio import ioprepped, IOAttrs if TYPE_CHECKING: pass @@ -33,26 +34,27 @@ class AssetType(Enum): COLLISION_MESH = 'collision_mesh' -class AssetPackageFlavorManifestValue(entity.CompoundValue): +@ioprepped +@dataclass +class AssetPackageFlavorManifest: """A manifest of asset info for a specific flavor of an asset package.""" - assetfiles = entity.DictField('assetfiles', str, entity.StringValue()) + assetfiles: Annotated[dict[str, str], + IOAttrs('assetfiles')] = field(default_factory=dict) -class AssetPackageFlavorManifest(entity.EntityMixin, - AssetPackageFlavorManifestValue): - """A self contained AssetPackageFlavorManifestValue.""" - - -class AssetPackageBuildState(entity.Entity): +@ioprepped +@dataclass +class AssetPackageBuildState: """Contains info about an in-progress asset cloud build.""" # Asset names still being built. - in_progress_builds = entity.ListField('b', entity.StringValue()) + in_progress_builds: Annotated[list[str], + IOAttrs('b')] = field(default_factory=list) # The initial number of assets needing to be built. - initial_build_count = entity.Field('c', entity.IntValue()) + initial_build_count: Annotated[int, IOAttrs('c')] = 0 # Build error string. If this is present, it should be presented # to the user and they should required to explicitly restart the build # in some way if desired. - error = entity.Field('e', entity.OptionalStringValue()) + error: Annotated[Optional[str], IOAttrs('e')] = None diff --git a/dist/ba_data/python/bacommon/net.py b/dist/ba_data/python/bacommon/net.py index 1b5a7f2..bd4ef1c 100644 --- a/dist/ba_data/python/bacommon/net.py +++ b/dist/ba_data/python/bacommon/net.py @@ -4,33 +4,36 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Optional, List, Dict, Any, Tuple -from dataclasses import dataclass +from typing import TYPE_CHECKING, Optional, Any, Annotated +from dataclasses import dataclass, field -from efro import entity -from efro.dataclassio import ioprepped +from efro.dataclassio import ioprepped, IOAttrs if TYPE_CHECKING: pass -class ServerNodeEntry(entity.CompoundValue): +@ioprepped +@dataclass +class ServerNodeEntry: """Information about a specific server.""" - region = entity.Field('r', entity.StringValue()) - address = entity.Field('a', entity.StringValue()) - port = entity.Field('p', entity.IntValue()) + region: Annotated[str, IOAttrs('r')] + address: Annotated[str, IOAttrs('a')] + port: Annotated[int, IOAttrs('p')] -class ServerNodeQueryResponse(entity.Entity): +@ioprepped +@dataclass +class ServerNodeQueryResponse: """A response to a query about server-nodes.""" # If present, something went wrong, and this describes it. - error = entity.Field('e', entity.OptionalStringValue(store_default=False)) + error: Annotated[Optional[str], IOAttrs('e', store_default=False)] = None # The set of servernodes. - servers = entity.CompoundListField('s', - ServerNodeEntry(), - store_default=False) + servers: Annotated[list[ServerNodeEntry], + IOAttrs('s', store_default=False)] = field( + default_factory=list) @ioprepped @@ -52,10 +55,13 @@ class PrivateHostingConfig: playlist_name: str = 'Unknown' randomize: bool = False tutorial: bool = False - custom_team_names: Optional[Tuple[str, str]] = None - custom_team_colors: Optional[Tuple[Tuple[float, float, float], - Tuple[float, float, float]]] = None - playlist: Optional[List[Dict[str, Any]]] = None + custom_team_names: Optional[tuple[str, str]] = None + custom_team_colors: Optional[tuple[tuple[float, float, float], + tuple[float, float, float]]] = None + playlist: Optional[list[dict[str, Any]]] = None + exit_minutes: float = 120.0 + exit_minutes_unclean: float = 180.0 + exit_minutes_idle: float = 10.0 @ioprepped diff --git a/dist/ba_data/python/bacommon/servermanager.py b/dist/ba_data/python/bacommon/servermanager.py index c4a2246..b767006 100644 --- a/dist/ba_data/python/bacommon/servermanager.py +++ b/dist/ba_data/python/bacommon/servermanager.py @@ -5,7 +5,7 @@ from __future__ import annotations from enum import Enum from dataclasses import field, dataclass -from typing import TYPE_CHECKING, List, Optional, Tuple, Dict, Any +from typing import TYPE_CHECKING, Optional, Any from efro.dataclassio import ioprepped @@ -33,7 +33,7 @@ class ServerConfig: # IDs of server admins. Server admins are not kickable through the default # kick vote system and they are able to kick players without a vote. To get # your account id, enter 'getaccountid' in settings->advanced->enter-code. - admins: List[str] = field(default_factory=list) + admins: list[str] = field(default_factory=list) # Whether the default kick-voting system is enabled. enable_default_kick_voting: bool = True @@ -51,10 +51,11 @@ class ServerConfig: # exposed but I'll try to add that soon. max_party_size: int = 6 - # Options here are 'ffa' (free-for-all) and 'teams' + # Options here are 'ffa' (free-for-all), 'teams' and 'coop' (cooperative) # This value is ignored if you supply a playlist_code (see below). session_type: str = 'ffa' + # Playlist-code for teams or free-for-all mode sessions. # To host your own custom playlists, use the 'share' functionality in the # playlist editor in the regular version of the game. # This will give you a numeric code you can enter here to host that @@ -63,7 +64,7 @@ class ServerConfig: # Alternately, you can embed playlist data here instead of using codes. # Make sure to set session_type to the correct type for the data here. - playlist_inline: Optional[List[Dict[str, Any]]] = None + playlist_inline: Optional[list[dict[str, Any]]] = None # Whether to shuffle the playlist or play its games in designated order. playlist_shuffle: bool = True @@ -72,6 +73,15 @@ class ServerConfig: # (teams mode only). auto_balance_teams: bool = True + # The campaign used when in co-op session mode. + # Do print(ba.app.campaigns) to see available campaign names. + coop_campaign: str = 'Easy' + + # The level name within the campaign used in co-op session mode. + # For campaign name FOO, do print(ba.app.campaigns['FOO'].levels) to see + # available level names. + coop_level: str = 'Onslaught Training' + # Whether to enable telnet access. # IMPORTANT: This option is no longer available, as it was being used # for exploits. Live access to the running server is still possible through @@ -121,11 +131,11 @@ class ServerConfig: show_tutorial: bool = False # Team names (teams mode only). - team_names: Optional[Tuple[str, str]] = None + team_names: Optional[tuple[str, str]] = None # Team colors (teams mode only). - team_colors: Optional[Tuple[Tuple[float, float, float], - Tuple[float, float, float]]] = None + team_colors: Optional[tuple[tuple[float, float, float], + tuple[float, float, float]]] = None # (internal) stress-testing mode. stress_test_players: Optional[int] = None @@ -161,15 +171,15 @@ class ShutdownCommand(ServerCommand): class ChatMessageCommand(ServerCommand): """Chat message from the server.""" message: str - clients: Optional[List[int]] + clients: Optional[list[int]] @dataclass class ScreenMessageCommand(ServerCommand): """Screen-message from the server.""" message: str - color: Optional[Tuple[float, float, float]] - clients: Optional[List[int]] + color: Optional[tuple[float, float, float]] + clients: Optional[list[int]] @dataclass diff --git a/dist/ba_data/python/bastd/__pycache__/__init__.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/__pycache__/__init__.cpython-38.opt-1.pyc deleted file mode 100644 index fc2f5b1..0000000 Binary files a/dist/ba_data/python/bastd/__pycache__/__init__.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/bastd/__pycache__/__init__.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/__pycache__/__init__.cpython-39.opt-1.pyc new file mode 100644 index 0000000..2ca5c8e Binary files /dev/null and b/dist/ba_data/python/bastd/__pycache__/__init__.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/__pycache__/__init__.cpython-39.pyc b/dist/ba_data/python/bastd/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..0d6abd4 Binary files /dev/null and b/dist/ba_data/python/bastd/__pycache__/__init__.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/__pycache__/appdelegate.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/__pycache__/appdelegate.cpython-38.opt-1.pyc deleted file mode 100644 index c07852a..0000000 Binary files a/dist/ba_data/python/bastd/__pycache__/appdelegate.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/bastd/__pycache__/appdelegate.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/__pycache__/appdelegate.cpython-39.opt-1.pyc new file mode 100644 index 0000000..8b659f2 Binary files /dev/null and b/dist/ba_data/python/bastd/__pycache__/appdelegate.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/__pycache__/appdelegate.cpython-39.pyc b/dist/ba_data/python/bastd/__pycache__/appdelegate.cpython-39.pyc new file mode 100644 index 0000000..af79f71 Binary files /dev/null and b/dist/ba_data/python/bastd/__pycache__/appdelegate.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/__pycache__/gameutils.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/__pycache__/gameutils.cpython-39.opt-1.pyc similarity index 57% rename from dist/ba_data/python/bastd/__pycache__/gameutils.cpython-38.opt-1.pyc rename to dist/ba_data/python/bastd/__pycache__/gameutils.cpython-39.opt-1.pyc index dd81cf4..c297a21 100644 Binary files a/dist/ba_data/python/bastd/__pycache__/gameutils.cpython-38.opt-1.pyc and b/dist/ba_data/python/bastd/__pycache__/gameutils.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/__pycache__/gameutils.cpython-39.pyc b/dist/ba_data/python/bastd/__pycache__/gameutils.cpython-39.pyc new file mode 100644 index 0000000..d46355b Binary files /dev/null and b/dist/ba_data/python/bastd/__pycache__/gameutils.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/__pycache__/mainmenu.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/__pycache__/mainmenu.cpython-38.opt-1.pyc deleted file mode 100644 index 01e49fb..0000000 Binary files a/dist/ba_data/python/bastd/__pycache__/mainmenu.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/bastd/__pycache__/mainmenu.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/__pycache__/mainmenu.cpython-39.opt-1.pyc new file mode 100644 index 0000000..9213cd8 Binary files /dev/null and b/dist/ba_data/python/bastd/__pycache__/mainmenu.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/__pycache__/mainmenu.cpython-39.pyc b/dist/ba_data/python/bastd/__pycache__/mainmenu.cpython-39.pyc new file mode 100644 index 0000000..c58c164 Binary files /dev/null and b/dist/ba_data/python/bastd/__pycache__/mainmenu.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/__pycache__/maps.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/__pycache__/maps.cpython-38.opt-1.pyc deleted file mode 100644 index 6f7bcb0..0000000 Binary files a/dist/ba_data/python/bastd/__pycache__/maps.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/bastd/__pycache__/maps.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/__pycache__/maps.cpython-39.opt-1.pyc new file mode 100644 index 0000000..bda4a6b Binary files /dev/null and b/dist/ba_data/python/bastd/__pycache__/maps.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/__pycache__/maps.cpython-39.pyc b/dist/ba_data/python/bastd/__pycache__/maps.cpython-39.pyc new file mode 100644 index 0000000..b1ef1f4 Binary files /dev/null and b/dist/ba_data/python/bastd/__pycache__/maps.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/__pycache__/stdmap.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/__pycache__/stdmap.cpython-39.opt-1.pyc new file mode 100644 index 0000000..813ced5 Binary files /dev/null and b/dist/ba_data/python/bastd/__pycache__/stdmap.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/__pycache__/tutorial.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/__pycache__/tutorial.cpython-38.opt-1.pyc deleted file mode 100644 index 68d740a..0000000 Binary files a/dist/ba_data/python/bastd/__pycache__/tutorial.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/bastd/__pycache__/tutorial.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/__pycache__/tutorial.cpython-39.opt-1.pyc new file mode 100644 index 0000000..2e727d1 Binary files /dev/null and b/dist/ba_data/python/bastd/__pycache__/tutorial.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/__pycache__/tutorial.cpython-39.pyc b/dist/ba_data/python/bastd/__pycache__/tutorial.cpython-39.pyc new file mode 100644 index 0000000..8c97f66 Binary files /dev/null and b/dist/ba_data/python/bastd/__pycache__/tutorial.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/activity/__pycache__/__init__.cpython-38.pyc b/dist/ba_data/python/bastd/activity/__pycache__/__init__.cpython-38.pyc index 84b7500..13c38f8 100644 Binary files a/dist/ba_data/python/bastd/activity/__pycache__/__init__.cpython-38.pyc and b/dist/ba_data/python/bastd/activity/__pycache__/__init__.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/activity/__pycache__/__init__.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/activity/__pycache__/__init__.cpython-39.opt-1.pyc new file mode 100644 index 0000000..de6c0cc Binary files /dev/null and b/dist/ba_data/python/bastd/activity/__pycache__/__init__.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/activity/__pycache__/__init__.cpython-39.pyc b/dist/ba_data/python/bastd/activity/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..f856b4d Binary files /dev/null and b/dist/ba_data/python/bastd/activity/__pycache__/__init__.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/activity/__pycache__/coopjoin.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/activity/__pycache__/coopjoin.cpython-39.opt-1.pyc new file mode 100644 index 0000000..03098af Binary files /dev/null and b/dist/ba_data/python/bastd/activity/__pycache__/coopjoin.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/activity/__pycache__/coopscore.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/activity/__pycache__/coopscore.cpython-38.opt-1.pyc index cd464a3..26dd171 100644 Binary files a/dist/ba_data/python/bastd/activity/__pycache__/coopscore.cpython-38.opt-1.pyc and b/dist/ba_data/python/bastd/activity/__pycache__/coopscore.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/activity/__pycache__/coopscore.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/activity/__pycache__/coopscore.cpython-39.opt-1.pyc new file mode 100644 index 0000000..89cb263 Binary files /dev/null and b/dist/ba_data/python/bastd/activity/__pycache__/coopscore.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/activity/__pycache__/drawscore.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/activity/__pycache__/drawscore.cpython-39.opt-1.pyc new file mode 100644 index 0000000..0795146 Binary files /dev/null and b/dist/ba_data/python/bastd/activity/__pycache__/drawscore.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/activity/__pycache__/dualteamscore.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/activity/__pycache__/dualteamscore.cpython-39.opt-1.pyc new file mode 100644 index 0000000..8a73734 Binary files /dev/null and b/dist/ba_data/python/bastd/activity/__pycache__/dualteamscore.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/activity/__pycache__/freeforallvictory.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/activity/__pycache__/freeforallvictory.cpython-39.opt-1.pyc new file mode 100644 index 0000000..75a2fb1 Binary files /dev/null and b/dist/ba_data/python/bastd/activity/__pycache__/freeforallvictory.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/activity/__pycache__/multiteamjoin.cpython-38.pyc b/dist/ba_data/python/bastd/activity/__pycache__/multiteamjoin.cpython-38.pyc index 5206e97..0805a80 100644 Binary files a/dist/ba_data/python/bastd/activity/__pycache__/multiteamjoin.cpython-38.pyc and b/dist/ba_data/python/bastd/activity/__pycache__/multiteamjoin.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/activity/__pycache__/multiteamjoin.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/activity/__pycache__/multiteamjoin.cpython-39.opt-1.pyc new file mode 100644 index 0000000..85a87d9 Binary files /dev/null and b/dist/ba_data/python/bastd/activity/__pycache__/multiteamjoin.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/activity/__pycache__/multiteamjoin.cpython-39.pyc b/dist/ba_data/python/bastd/activity/__pycache__/multiteamjoin.cpython-39.pyc new file mode 100644 index 0000000..91404d4 Binary files /dev/null and b/dist/ba_data/python/bastd/activity/__pycache__/multiteamjoin.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/activity/__pycache__/multiteamscore.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/activity/__pycache__/multiteamscore.cpython-39.opt-1.pyc new file mode 100644 index 0000000..80d8c1f Binary files /dev/null and b/dist/ba_data/python/bastd/activity/__pycache__/multiteamscore.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/activity/__pycache__/multiteamscore.cpython-39.pyc b/dist/ba_data/python/bastd/activity/__pycache__/multiteamscore.cpython-39.pyc new file mode 100644 index 0000000..6720d2a Binary files /dev/null and b/dist/ba_data/python/bastd/activity/__pycache__/multiteamscore.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/activity/__pycache__/multiteamvictory.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/activity/__pycache__/multiteamvictory.cpython-39.opt-1.pyc new file mode 100644 index 0000000..4fb8fd8 Binary files /dev/null and b/dist/ba_data/python/bastd/activity/__pycache__/multiteamvictory.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/activity/__pycache__/multiteamvictory.cpython-39.pyc b/dist/ba_data/python/bastd/activity/__pycache__/multiteamvictory.cpython-39.pyc new file mode 100644 index 0000000..8242a30 Binary files /dev/null and b/dist/ba_data/python/bastd/activity/__pycache__/multiteamvictory.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/activity/coopjoin.py b/dist/ba_data/python/bastd/activity/coopjoin.py index 1bb614a..9c44baa 100644 --- a/dist/ba_data/python/bastd/activity/coopjoin.py +++ b/dist/ba_data/python/bastd/activity/coopjoin.py @@ -11,7 +11,7 @@ import ba from ba.internal import JoinActivity if TYPE_CHECKING: - from typing import Any, Dict, List, Optional, Sequence, Union + from typing import Any, Optional, Sequence, Union class CoopJoinActivity(JoinActivity): @@ -54,7 +54,7 @@ class CoopJoinActivity(JoinActivity): ControlsGuide(delay=1.0).autoretain() def _on_got_scores_to_beat(self, - scores: Optional[List[Dict[str, Any]]]) -> None: + scores: Optional[list[dict[str, Any]]]) -> None: # pylint: disable=too-many-locals # pylint: disable=too-many-statements from efro.util import asserttype diff --git a/dist/ba_data/python/bastd/activity/coopscore.py b/dist/ba_data/python/bastd/activity/coopscore.py index b8678cd..9df3bad 100644 --- a/dist/ba_data/python/bastd/activity/coopscore.py +++ b/dist/ba_data/python/bastd/activity/coopscore.py @@ -14,7 +14,7 @@ from bastd.actor.text import Text from bastd.actor.zoomtext import ZoomText if TYPE_CHECKING: - from typing import Optional, Tuple, List, Dict, Any, Sequence + from typing import Optional, Any, Sequence from bastd.ui.store.button import StoreButton from bastd.ui.league.rankbutton import LeagueRankButton @@ -96,7 +96,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): self._game_config_str: Optional[str] = None # Ui bits. - self._corner_button_offs: Optional[Tuple[float, float]] = None + self._corner_button_offs: Optional[tuple[float, float]] = None self._league_rank_button: Optional[LeagueRankButton] = None self._store_button_instance: Optional[StoreButton] = None self._restart_button: Optional[ba.Widget] = None @@ -110,7 +110,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): self._is_more_levels: Optional[bool] = None self._next_level_name: Optional[str] = None self._show_friend_scores: Optional[bool] = None - self._show_info: Optional[Dict[str, Any]] = None + self._show_info: Optional[dict[str, Any]] = None self._name_str: Optional[str] = None self._friends_loading_status: Optional[ba.Actor] = None self._score_loading_status: Optional[ba.Actor] = None @@ -118,7 +118,13 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): self._tournament_time_remaining_text: Optional[Text] = None self._tournament_time_remaining_text_timer: Optional[ba.Timer] = None - self._playerinfos: List[ba.PlayerInfo] = settings['playerinfos'] + # Stuff for activity skip by pressing button + self._birth_time = ba.time() + self._min_view_time = 5.0 + self._allow_server_transition = False + self._server_transitioning: Optional[bool] = None + + self._playerinfos: list[ba.PlayerInfo] = settings['playerinfos'] assert isinstance(self._playerinfos, list) assert (isinstance(i, ba.PlayerInfo) for i in self._playerinfos) @@ -485,6 +491,46 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): if self._store_button_instance is not None: self._store_button_instance.set_position((pos_x + 100, pos_y)) + def _player_press(self) -> None: + # (Only for headless builds). + + # 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): + self._server_transitioning = _ba.app.server.handle_transition() + assert isinstance(self._server_transitioning, bool) + + # If server-mode is handling this, don't do anything ourself. + if self._server_transitioning is True: + return + + # Otherwise restart current level. + self._campaign.set_selected_level(self._level_name) + with ba.Context(self): + self.end({'outcome': 'restart'}) + + def _safe_assign(self, player: ba.Player) -> None: + # (Only for headless builds). + + # 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( + (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) + + 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()) + + ba.timer(time_till_assign, ba.WeakCall(self._safe_assign, player)) + def on_begin(self) -> None: # FIXME: Clean this up. # pylint: disable=too-many-statements @@ -582,19 +628,35 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): color=(0.5, 0.7, 0.5, 1), position=(0, 230)).autoretain() - adisp = _ba.get_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() - assert txt.node - txt.node.client_only = True + if ba.app.server is None: + # If we're running in normal non-headless build, show this text + # because only host can continue the game. + adisp = _ba.get_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() + 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() if self._score is not None: ba.timer(0.35, @@ -769,7 +831,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): scale = 0.65 elif p_count == 4: scale = 0.5 - times: List[Tuple[float, float]] = [] + times: list[tuple[float, float]] = [] for i in range(display_count): times.insert(random.randrange(0, len(times) + 1), @@ -867,7 +929,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): 'loop': False })).autoretain() - def _got_friend_score_results(self, results: Optional[List[Any]]) -> None: + def _got_friend_score_results(self, results: Optional[list[Any]]) -> None: # FIXME: tidy this up # pylint: disable=too-many-locals @@ -928,7 +990,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): while len(results) < 5: results.append([0, '-', False]) results = results[:5] - times: List[Tuple[float, float]] = [] + times: list[tuple[float, float]] = [] for i in range(len(results)): times.insert(random.randrange(0, len(times) + 1), @@ -982,7 +1044,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): transition=Text.Transition.IN_RIGHT, transition_delay=tdelay2).autoretain() - def _got_score_results(self, results: Optional[Dict[str, Any]]) -> None: + def _got_score_results(self, results: Optional[dict[str, Any]]) -> None: # FIXME: tidy this up # pylint: disable=too-many-locals @@ -1080,7 +1142,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): while len(self._show_info['tops']) < 10: self._show_info['tops'].append([0, '-']) - times: List[Tuple[float, float]] = [] + times: list[tuple[float, float]] = [] for i in range(len(self._show_info['tops'])): times.insert( random.randrange(0, diff --git a/dist/ba_data/python/bastd/activity/drawscore.py b/dist/ba_data/python/bastd/activity/drawscore.py index c12b82b..38109f7 100644 --- a/dist/ba_data/python/bastd/activity/drawscore.py +++ b/dist/ba_data/python/bastd/activity/drawscore.py @@ -11,7 +11,7 @@ from bastd.activity.multiteamscore import MultiTeamScoreScreenActivity from bastd.actor.zoomtext import ZoomText if TYPE_CHECKING: - from typing import Any, Dict + pass class DrawScoreScreenActivity(MultiTeamScoreScreenActivity): diff --git a/dist/ba_data/python/bastd/activity/dualteamscore.py b/dist/ba_data/python/bastd/activity/dualteamscore.py index b031bb8..aae7fde 100644 --- a/dist/ba_data/python/bastd/activity/dualteamscore.py +++ b/dist/ba_data/python/bastd/activity/dualteamscore.py @@ -11,7 +11,7 @@ from bastd.activity.multiteamscore import MultiTeamScoreScreenActivity from bastd.actor.zoomtext import ZoomText if TYPE_CHECKING: - from typing import Any, Dict + pass class TeamVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): diff --git a/dist/ba_data/python/bastd/activity/freeforallvictory.py b/dist/ba_data/python/bastd/activity/freeforallvictory.py index 1015058..0aab714 100644 --- a/dist/ba_data/python/bastd/activity/freeforallvictory.py +++ b/dist/ba_data/python/bastd/activity/freeforallvictory.py @@ -10,7 +10,7 @@ import ba from bastd.activity.multiteamscore import MultiTeamScoreScreenActivity if TYPE_CHECKING: - from typing import Any, Dict, Optional, Set, Tuple + from typing import Any, Optional class FreeForAllVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): @@ -71,7 +71,7 @@ class FreeForAllVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): scale=1.2, x_offset=-110.0) - sound_times: Set[float] = set() + sound_times: set[float] = set() def _scoretxt(text: str, x_offs: float, @@ -260,7 +260,7 @@ class FreeForAllVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): v_offs -= spacing def _safe_animate(self, node: Optional[ba.Node], attr: str, - keys: Dict[float, float]) -> None: + 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 9c27859..a1a2586 100644 --- a/dist/ba_data/python/bastd/activity/multiteamjoin.py +++ b/dist/ba_data/python/bastd/activity/multiteamjoin.py @@ -11,7 +11,7 @@ from ba.internal import JoinActivity from bastd.actor.text import Text if TYPE_CHECKING: - from typing import Any, Dict, Optional + from typing import Optional class MultiTeamJoinActivity(JoinActivity): diff --git a/dist/ba_data/python/bastd/activity/multiteamscore.py b/dist/ba_data/python/bastd/activity/multiteamscore.py index ba87a61..3701ff8 100644 --- a/dist/ba_data/python/bastd/activity/multiteamscore.py +++ b/dist/ba_data/python/bastd/activity/multiteamscore.py @@ -11,7 +11,7 @@ from bastd.actor.text import Text from bastd.actor.image import Image if TYPE_CHECKING: - from typing import Any, Dict, Optional, Union + from typing import Optional, Union class MultiTeamScoreScreenActivity(ScoreScreenActivity): diff --git a/dist/ba_data/python/bastd/activity/multiteamvictory.py b/dist/ba_data/python/bastd/activity/multiteamvictory.py index 9acc784..061ad68 100644 --- a/dist/ba_data/python/bastd/activity/multiteamvictory.py +++ b/dist/ba_data/python/bastd/activity/multiteamvictory.py @@ -10,7 +10,7 @@ import ba from bastd.activity.multiteamscore import MultiTeamScoreScreenActivity if TYPE_CHECKING: - from typing import Any, Dict, List, Tuple, Optional + from typing import Optional class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): @@ -51,7 +51,7 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): ba.timer(4.6, ba.Call(ba.playsound, self._score_display_sound)) # Score / Name / Player-record. - player_entries: List[Tuple[int, str, ba.PlayerRecord]] = [] + player_entries: list[tuple[int, str, ba.PlayerRecord]] = [] # Note: for ffa, exclude players who haven't entered the game yet. if self._is_ffa: diff --git a/dist/ba_data/python/bastd/actor/__pycache__/__init__.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/__init__.cpython-38.opt-1.pyc deleted file mode 100644 index feeea88..0000000 Binary files a/dist/ba_data/python/bastd/actor/__pycache__/__init__.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/__init__.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/__init__.cpython-39.opt-1.pyc new file mode 100644 index 0000000..de6c0cc Binary files /dev/null and b/dist/ba_data/python/bastd/actor/__pycache__/__init__.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/__init__.cpython-39.pyc b/dist/ba_data/python/bastd/actor/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..f143963 Binary files /dev/null and b/dist/ba_data/python/bastd/actor/__pycache__/__init__.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/background.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/background.cpython-39.opt-1.pyc new file mode 100644 index 0000000..1258a40 Binary files /dev/null and b/dist/ba_data/python/bastd/actor/__pycache__/background.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/background.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/background.cpython-39.pyc similarity index 53% rename from dist/ba_data/python/bastd/actor/__pycache__/background.cpython-38.opt-1.pyc rename to dist/ba_data/python/bastd/actor/__pycache__/background.cpython-39.pyc index 7337efc..5941a45 100644 Binary files a/dist/ba_data/python/bastd/actor/__pycache__/background.cpython-38.opt-1.pyc and b/dist/ba_data/python/bastd/actor/__pycache__/background.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/bomb.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/bomb.cpython-38.opt-1.pyc deleted file mode 100644 index 3730229..0000000 Binary files a/dist/ba_data/python/bastd/actor/__pycache__/bomb.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/bomb.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/bomb.cpython-39.opt-1.pyc new file mode 100644 index 0000000..a932be8 Binary files /dev/null and b/dist/ba_data/python/bastd/actor/__pycache__/bomb.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/bomb.cpython-39.pyc b/dist/ba_data/python/bastd/actor/__pycache__/bomb.cpython-39.pyc new file mode 100644 index 0000000..dcb9d2e Binary files /dev/null and b/dist/ba_data/python/bastd/actor/__pycache__/bomb.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/controlsguide.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/controlsguide.cpython-38.opt-1.pyc deleted file mode 100644 index b689f0f..0000000 Binary files a/dist/ba_data/python/bastd/actor/__pycache__/controlsguide.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/controlsguide.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/controlsguide.cpython-39.opt-1.pyc new file mode 100644 index 0000000..8b74247 Binary files /dev/null and b/dist/ba_data/python/bastd/actor/__pycache__/controlsguide.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/controlsguide.cpython-39.pyc b/dist/ba_data/python/bastd/actor/__pycache__/controlsguide.cpython-39.pyc new file mode 100644 index 0000000..165d956 Binary files /dev/null and b/dist/ba_data/python/bastd/actor/__pycache__/controlsguide.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/flag.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/flag.cpython-39.opt-1.pyc similarity index 51% rename from dist/ba_data/python/bastd/actor/__pycache__/flag.cpython-38.opt-1.pyc rename to dist/ba_data/python/bastd/actor/__pycache__/flag.cpython-39.opt-1.pyc index 7e99c77..d89d5f0 100644 Binary files a/dist/ba_data/python/bastd/actor/__pycache__/flag.cpython-38.opt-1.pyc and b/dist/ba_data/python/bastd/actor/__pycache__/flag.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/flag.cpython-39.pyc b/dist/ba_data/python/bastd/actor/__pycache__/flag.cpython-39.pyc new file mode 100644 index 0000000..1fb19d2 Binary files /dev/null and b/dist/ba_data/python/bastd/actor/__pycache__/flag.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/image.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/image.cpython-38.opt-1.pyc deleted file mode 100644 index 4375d27..0000000 Binary files a/dist/ba_data/python/bastd/actor/__pycache__/image.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/image.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/image.cpython-39.opt-1.pyc new file mode 100644 index 0000000..50a2849 Binary files /dev/null and b/dist/ba_data/python/bastd/actor/__pycache__/image.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/image.cpython-39.pyc b/dist/ba_data/python/bastd/actor/__pycache__/image.cpython-39.pyc new file mode 100644 index 0000000..e6e10d5 Binary files /dev/null and b/dist/ba_data/python/bastd/actor/__pycache__/image.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/onscreencountdown.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/onscreencountdown.cpython-39.opt-1.pyc new file mode 100644 index 0000000..1913e1c Binary files /dev/null and b/dist/ba_data/python/bastd/actor/__pycache__/onscreencountdown.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/onscreencountdown.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/onscreencountdown.cpython-39.pyc similarity index 67% rename from dist/ba_data/python/bastd/actor/__pycache__/onscreencountdown.cpython-38.opt-1.pyc rename to dist/ba_data/python/bastd/actor/__pycache__/onscreencountdown.cpython-39.pyc index c86f484..e100644 100644 Binary files a/dist/ba_data/python/bastd/actor/__pycache__/onscreencountdown.cpython-38.opt-1.pyc and b/dist/ba_data/python/bastd/actor/__pycache__/onscreencountdown.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/onscreentimer.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/onscreentimer.cpython-38.opt-1.pyc deleted file mode 100644 index 3252496..0000000 Binary files a/dist/ba_data/python/bastd/actor/__pycache__/onscreentimer.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/onscreentimer.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/onscreentimer.cpython-39.opt-1.pyc new file mode 100644 index 0000000..21a1fd8 Binary files /dev/null and b/dist/ba_data/python/bastd/actor/__pycache__/onscreentimer.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/onscreentimer.cpython-39.pyc b/dist/ba_data/python/bastd/actor/__pycache__/onscreentimer.cpython-39.pyc new file mode 100644 index 0000000..36b659b Binary files /dev/null and b/dist/ba_data/python/bastd/actor/__pycache__/onscreentimer.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/playerspaz.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/playerspaz.cpython-38.opt-1.pyc deleted file mode 100644 index dd49e13..0000000 Binary files a/dist/ba_data/python/bastd/actor/__pycache__/playerspaz.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/playerspaz.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/playerspaz.cpython-39.opt-1.pyc new file mode 100644 index 0000000..077556e Binary files /dev/null and b/dist/ba_data/python/bastd/actor/__pycache__/playerspaz.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/playerspaz.cpython-39.pyc b/dist/ba_data/python/bastd/actor/__pycache__/playerspaz.cpython-39.pyc new file mode 100644 index 0000000..d58915c Binary files /dev/null and b/dist/ba_data/python/bastd/actor/__pycache__/playerspaz.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/popuptext.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/popuptext.cpython-39.opt-1.pyc new file mode 100644 index 0000000..f09059d Binary files /dev/null and b/dist/ba_data/python/bastd/actor/__pycache__/popuptext.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/popuptext.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/popuptext.cpython-39.pyc similarity index 79% rename from dist/ba_data/python/bastd/actor/__pycache__/popuptext.cpython-38.opt-1.pyc rename to dist/ba_data/python/bastd/actor/__pycache__/popuptext.cpython-39.pyc index 209ae79..596d0a2 100644 Binary files a/dist/ba_data/python/bastd/actor/__pycache__/popuptext.cpython-38.opt-1.pyc and b/dist/ba_data/python/bastd/actor/__pycache__/popuptext.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/powerupbox.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/powerupbox.cpython-39.opt-1.pyc new file mode 100644 index 0000000..8715772 Binary files /dev/null and b/dist/ba_data/python/bastd/actor/__pycache__/powerupbox.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/powerupbox.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/powerupbox.cpython-39.pyc similarity index 56% rename from dist/ba_data/python/bastd/actor/__pycache__/powerupbox.cpython-38.opt-1.pyc rename to dist/ba_data/python/bastd/actor/__pycache__/powerupbox.cpython-39.pyc index bf00871..aa1ce02 100644 Binary files a/dist/ba_data/python/bastd/actor/__pycache__/powerupbox.cpython-38.opt-1.pyc and b/dist/ba_data/python/bastd/actor/__pycache__/powerupbox.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/respawnicon.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/respawnicon.cpython-38.opt-1.pyc deleted file mode 100644 index a9547a6..0000000 Binary files a/dist/ba_data/python/bastd/actor/__pycache__/respawnicon.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/respawnicon.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/respawnicon.cpython-39.opt-1.pyc new file mode 100644 index 0000000..bd48ef3 Binary files /dev/null and b/dist/ba_data/python/bastd/actor/__pycache__/respawnicon.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/respawnicon.cpython-39.pyc b/dist/ba_data/python/bastd/actor/__pycache__/respawnicon.cpython-39.pyc new file mode 100644 index 0000000..552821b Binary files /dev/null and b/dist/ba_data/python/bastd/actor/__pycache__/respawnicon.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/scoreboard.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/scoreboard.cpython-38.opt-1.pyc deleted file mode 100644 index 887fe24..0000000 Binary files a/dist/ba_data/python/bastd/actor/__pycache__/scoreboard.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/scoreboard.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/scoreboard.cpython-39.opt-1.pyc new file mode 100644 index 0000000..7508136 Binary files /dev/null and b/dist/ba_data/python/bastd/actor/__pycache__/scoreboard.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/scoreboard.cpython-39.pyc b/dist/ba_data/python/bastd/actor/__pycache__/scoreboard.cpython-39.pyc new file mode 100644 index 0000000..b8f5270 Binary files /dev/null and b/dist/ba_data/python/bastd/actor/__pycache__/scoreboard.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/spawner.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/spawner.cpython-39.opt-1.pyc new file mode 100644 index 0000000..08fd221 Binary files /dev/null and b/dist/ba_data/python/bastd/actor/__pycache__/spawner.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/spaz.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/spaz.cpython-38.opt-1.pyc deleted file mode 100644 index 21ef982..0000000 Binary files a/dist/ba_data/python/bastd/actor/__pycache__/spaz.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/spaz.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/spaz.cpython-39.opt-1.pyc new file mode 100644 index 0000000..4bf68fc Binary files /dev/null and b/dist/ba_data/python/bastd/actor/__pycache__/spaz.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/spaz.cpython-39.pyc b/dist/ba_data/python/bastd/actor/__pycache__/spaz.cpython-39.pyc new file mode 100644 index 0000000..27c6ee2 Binary files /dev/null and b/dist/ba_data/python/bastd/actor/__pycache__/spaz.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/spazappearance.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/spazappearance.cpython-38.opt-1.pyc deleted file mode 100644 index 4951cfc..0000000 Binary files a/dist/ba_data/python/bastd/actor/__pycache__/spazappearance.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/spazappearance.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/spazappearance.cpython-39.opt-1.pyc new file mode 100644 index 0000000..c771825 Binary files /dev/null and b/dist/ba_data/python/bastd/actor/__pycache__/spazappearance.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/spazappearance.cpython-39.pyc b/dist/ba_data/python/bastd/actor/__pycache__/spazappearance.cpython-39.pyc new file mode 100644 index 0000000..6b41d89 Binary files /dev/null and b/dist/ba_data/python/bastd/actor/__pycache__/spazappearance.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/spazbot.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/spazbot.cpython-38.opt-1.pyc deleted file mode 100644 index 9370107..0000000 Binary files a/dist/ba_data/python/bastd/actor/__pycache__/spazbot.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/spazbot.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/spazbot.cpython-39.opt-1.pyc new file mode 100644 index 0000000..bedf772 Binary files /dev/null and b/dist/ba_data/python/bastd/actor/__pycache__/spazbot.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/spazbot.cpython-39.pyc b/dist/ba_data/python/bastd/actor/__pycache__/spazbot.cpython-39.pyc new file mode 100644 index 0000000..aa265a8 Binary files /dev/null and b/dist/ba_data/python/bastd/actor/__pycache__/spazbot.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/spazfactory.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/spazfactory.cpython-39.opt-1.pyc new file mode 100644 index 0000000..b1d8b9c Binary files /dev/null and b/dist/ba_data/python/bastd/actor/__pycache__/spazfactory.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/spazfactory.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/spazfactory.cpython-39.pyc similarity index 59% rename from dist/ba_data/python/bastd/actor/__pycache__/spazfactory.cpython-38.opt-1.pyc rename to dist/ba_data/python/bastd/actor/__pycache__/spazfactory.cpython-39.pyc index 1041b68..c4e1719 100644 Binary files a/dist/ba_data/python/bastd/actor/__pycache__/spazfactory.cpython-38.opt-1.pyc and b/dist/ba_data/python/bastd/actor/__pycache__/spazfactory.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/text.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/text.cpython-38.opt-1.pyc deleted file mode 100644 index c051e52..0000000 Binary files a/dist/ba_data/python/bastd/actor/__pycache__/text.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/text.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/text.cpython-39.opt-1.pyc new file mode 100644 index 0000000..bb49c39 Binary files /dev/null and b/dist/ba_data/python/bastd/actor/__pycache__/text.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/text.cpython-39.pyc b/dist/ba_data/python/bastd/actor/__pycache__/text.cpython-39.pyc new file mode 100644 index 0000000..49eccb1 Binary files /dev/null and b/dist/ba_data/python/bastd/actor/__pycache__/text.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/tipstext.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/tipstext.cpython-39.opt-1.pyc new file mode 100644 index 0000000..ffb899e Binary files /dev/null and b/dist/ba_data/python/bastd/actor/__pycache__/tipstext.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/tipstext.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/tipstext.cpython-39.pyc similarity index 69% rename from dist/ba_data/python/bastd/actor/__pycache__/tipstext.cpython-38.opt-1.pyc rename to dist/ba_data/python/bastd/actor/__pycache__/tipstext.cpython-39.pyc index a190ad3..494ce49 100644 Binary files a/dist/ba_data/python/bastd/actor/__pycache__/tipstext.cpython-38.opt-1.pyc and b/dist/ba_data/python/bastd/actor/__pycache__/tipstext.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/zoomtext.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/zoomtext.cpython-38.opt-1.pyc deleted file mode 100644 index 410b02f..0000000 Binary files a/dist/ba_data/python/bastd/actor/__pycache__/zoomtext.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/zoomtext.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/actor/__pycache__/zoomtext.cpython-39.opt-1.pyc new file mode 100644 index 0000000..440bf10 Binary files /dev/null and b/dist/ba_data/python/bastd/actor/__pycache__/zoomtext.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/actor/__pycache__/zoomtext.cpython-39.pyc b/dist/ba_data/python/bastd/actor/__pycache__/zoomtext.cpython-39.pyc new file mode 100644 index 0000000..2337294 Binary files /dev/null and b/dist/ba_data/python/bastd/actor/__pycache__/zoomtext.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/actor/bomb.py b/dist/ba_data/python/bastd/actor/bomb.py index 99d7dd1..1c86f1f 100644 --- a/dist/ba_data/python/bastd/actor/bomb.py +++ b/dist/ba_data/python/bastd/actor/bomb.py @@ -14,7 +14,7 @@ import ba from bastd.gameutils import SharedObjects if TYPE_CHECKING: - from typing import Any, Sequence, Optional, Callable, List, Tuple, Type + from typing import Any, Sequence, Optional, Callable PlayerType = TypeVar('PlayerType', bound='ba.Player') @@ -693,7 +693,7 @@ class Bomb(ba.Actor): elif self.bomb_type == 'tnt': self.blast_radius *= 1.45 - self._explode_callbacks: List[Callable[[Bomb, Blast], Any]] = [] + self._explode_callbacks: list[Callable[[Bomb, Blast], Any]] = [] # The player this came from. self._source_player = source_player @@ -716,7 +716,7 @@ class Bomb(ba.Actor): # since players carrying those things and thus touching footing # objects will think they're on solid ground.. perhaps we don't # wanna add this even in the tnt case? - materials: Tuple[ba.Material, ...] + materials: tuple[ba.Material, ...] if self.bomb_type == 'tnt': materials = (factory.bomb_material, shared.footing_material, shared.object_material) @@ -847,7 +847,7 @@ class Bomb(ba.Actor): }) def get_source_player( - self, playertype: Type[PlayerType]) -> Optional[PlayerType]: + self, playertype: type[PlayerType]) -> Optional[PlayerType]: """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() diff --git a/dist/ba_data/python/bastd/actor/controlsguide.py b/dist/ba_data/python/bastd/actor/controlsguide.py index db0d8ce..70279a9 100644 --- a/dist/ba_data/python/bastd/actor/controlsguide.py +++ b/dist/ba_data/python/bastd/actor/controlsguide.py @@ -10,7 +10,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Tuple, Optional, Sequence, Union + from typing import Any, Optional, Sequence, Union class ControlsGuide(ba.Actor): @@ -24,7 +24,7 @@ class ControlsGuide(ba.Actor): """ def __init__(self, - position: Tuple[float, float] = (390.0, 120.0), + position: tuple[float, float] = (390.0, 120.0), scale: float = 1.0, delay: float = 0.0, lifespan: float = None, @@ -57,8 +57,8 @@ class ControlsGuide(ba.Actor): self._update_timer: Optional[ba.Timer] = None self._title_text: Optional[ba.Node] clr: Sequence[float] - extra_pos_1: Optional[Tuple[float, float]] - extra_pos_2: Optional[Tuple[float, float]] + extra_pos_1: Optional[tuple[float, float]] + extra_pos_2: Optional[tuple[float, float]] if ba.app.iircade_mode: xtweak = 0.2 ytweak = 0.2 diff --git a/dist/ba_data/python/bastd/actor/image.py b/dist/ba_data/python/bastd/actor/image.py index 2db08a1..915ea44 100644 --- a/dist/ba_data/python/bastd/actor/image.py +++ b/dist/ba_data/python/bastd/actor/image.py @@ -10,7 +10,7 @@ from typing import TYPE_CHECKING import ba if TYPE_CHECKING: - from typing import Any, Tuple, Sequence, Union, Dict, Optional + from typing import Any, Sequence, Union, Optional class Image(ba.Actor): @@ -33,13 +33,13 @@ class Image(ba.Actor): BOTTOM_CENTER = 'bottomCenter' def __init__(self, - texture: Union[ba.Texture, Dict[str, Any]], - position: Tuple[float, float] = (0, 0), + texture: Union[ba.Texture, dict[str, Any]], + position: tuple[float, float] = (0, 0), transition: Optional[Transition] = 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), + scale: tuple[float, float] = (100.0, 100.0), transition_out_delay: float = None, model_opaque: ba.Model = None, model_transparent: ba.Model = None, diff --git a/dist/ba_data/python/bastd/actor/playerspaz.py b/dist/ba_data/python/bastd/actor/playerspaz.py index 11f38ed..3e285de 100644 --- a/dist/ba_data/python/bastd/actor/playerspaz.py +++ b/dist/ba_data/python/bastd/actor/playerspaz.py @@ -10,7 +10,7 @@ import ba from bastd.actor.spaz import Spaz if TYPE_CHECKING: - from typing import Any, Sequence, Tuple, Optional, Type, Literal + from typing import Any, Sequence, Optional, Literal PlayerType = TypeVar('PlayerType', bound=ba.Player) TeamType = TypeVar('TeamType', bound=ba.Team) @@ -65,7 +65,7 @@ class PlayerSpaz(Spaz): powerups_expire=powerups_expire) self.last_player_attacked_by: Optional[ba.Player] = None self.last_attacked_time = 0.0 - self.last_attacked_type: Optional[Tuple[str, str]] = None + self.last_attacked_type: Optional[tuple[str, str]] = None self.held_count = 0 self.last_player_held_by: Optional[ba.Player] = None self._player = player @@ -77,17 +77,17 @@ class PlayerSpaz(Spaz): @overload def getplayer(self, - playertype: Type[PlayerType], + playertype: type[PlayerType], doraise: Literal[False] = False) -> Optional[PlayerType]: ... @overload - def getplayer(self, playertype: Type[PlayerType], + def getplayer(self, playertype: type[PlayerType], doraise: Literal[True]) -> PlayerType: ... def getplayer(self, - playertype: Type[PlayerType], + playertype: type[PlayerType], doraise: bool = False) -> Optional[PlayerType]: """Get the ba.Player associated with this Spaz. diff --git a/dist/ba_data/python/bastd/actor/powerupbox.py b/dist/ba_data/python/bastd/actor/powerupbox.py index aac89f0..bed2de5 100644 --- a/dist/ba_data/python/bastd/actor/powerupbox.py +++ b/dist/ba_data/python/bastd/actor/powerupbox.py @@ -11,7 +11,7 @@ import ba from bastd.gameutils import SharedObjects if TYPE_CHECKING: - from typing import List, Any, Optional, Sequence + from typing import Any, Optional, Sequence DEFAULT_POWERUP_INTERVAL = 8.0 @@ -133,14 +133,14 @@ class PowerupBoxFactory: actions=('impact_sound', self.drop_sound, 0.5, 0.1), ) - self._powerupdist: List[str] = [] + self._powerupdist: list[str] = [] for powerup, freq in get_default_powerup_distribution(): for _i in range(int(freq)): self._powerupdist.append(powerup) def get_random_powerup_type(self, forcetype: str = None, - excludetypes: List[str] = None) -> str: + excludetypes: list[str] = None) -> str: """Returns a random powerup type (string). See ba.Powerup.poweruptype for available type values. diff --git a/dist/ba_data/python/bastd/actor/respawnicon.py b/dist/ba_data/python/bastd/actor/respawnicon.py index 504a8e6..66c8bf1 100644 --- a/dist/ba_data/python/bastd/actor/respawnicon.py +++ b/dist/ba_data/python/bastd/actor/respawnicon.py @@ -10,7 +10,7 @@ from typing import TYPE_CHECKING import ba if TYPE_CHECKING: - from typing import Optional, Dict, Tuple + from typing import Optional class RespawnIcon: @@ -116,7 +116,7 @@ class RespawnIcon: """Is this icon still visible?""" return self._visible - def _get_context(self, player: ba.Player) -> Tuple[bool, float, Dict]: + def _get_context(self, player: ba.Player) -> tuple[bool, float, dict]: """Return info on where we should be shown and stored.""" activity = ba.getactivity() diff --git a/dist/ba_data/python/bastd/actor/scoreboard.py b/dist/ba_data/python/bastd/actor/scoreboard.py index 54e1312..8432946 100644 --- a/dist/ba_data/python/bastd/actor/scoreboard.py +++ b/dist/ba_data/python/bastd/actor/scoreboard.py @@ -10,7 +10,7 @@ from typing import TYPE_CHECKING import ba if TYPE_CHECKING: - from typing import Any, Optional, Sequence, Dict, Union + from typing import Any, Optional, Sequence, Union class _Entry: @@ -334,7 +334,7 @@ class Scoreboard: show up on boards if provided. """ self._flat_tex = ba.gettexture('null') - self._entries: Dict[int, _Entry] = {} + self._entries: dict[int, _Entry] = {} self._label = label self.score_split = score_split diff --git a/dist/ba_data/python/bastd/actor/spaz.py b/dist/ba_data/python/bastd/actor/spaz.py index 064b90b..45931bb 100644 --- a/dist/ba_data/python/bastd/actor/spaz.py +++ b/dist/ba_data/python/bastd/actor/spaz.py @@ -15,8 +15,7 @@ from bastd.actor.spazfactory import SpazFactory from bastd.gameutils import SharedObjects if TYPE_CHECKING: - from typing import (Any, Sequence, Optional, Dict, List, Union, Callable, - Tuple, Set) + from typing import Any, Sequence, Optional, Union, Callable from bastd.actor.spazfactory import SpazFactory POWERUP_WEAR_OFF_TIME = 20000 @@ -104,7 +103,7 @@ class Spaz(ba.Actor): self._hockey = activity.map.is_hockey else: self._hockey = False - self._punched_nodes: Set[ba.Node] = set() + self._punched_nodes: set[ba.Node] = set() self._cursed = False self._connected_to_player: Optional[ba.Player] = None materials = [ @@ -203,9 +202,9 @@ class Spaz(ba.Actor): self.last_run_time_ms = -9999 self._last_run_value = 0.0 self.last_bomb_time_ms = -9999 - self._turbo_filter_times: Dict[str, int] = {} + self._turbo_filter_times: dict[str, int] = {} self._turbo_filter_time_bucket = 0 - self._turbo_filter_counts: Dict[str, int] = {} + self._turbo_filter_counts: dict[str, int] = {} self.frozen = False self.shattered = False self._last_hit_time: Optional[int] = None @@ -213,7 +212,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], + self._dropped_bomb_callbacks: list[Callable[[Spaz, ba.Actor], Any]] = [] self._score_text: Optional[ba.Node] = None @@ -561,7 +560,7 @@ class Spaz(ba.Actor): def on_punched(self, damage: int) -> None: """Called when this spaz gets punched.""" - def get_death_points(self, how: ba.DeathType) -> Tuple[int, int]: + def get_death_points(self, how: ba.DeathType) -> tuple[int, int]: """Get the points awarded for killing this spaz.""" del how # Unused. num_hits = float(max(1, self._num_times_hit)) diff --git a/dist/ba_data/python/bastd/actor/spazappearance.py b/dist/ba_data/python/bastd/actor/spazappearance.py index 166b1f8..bc22940 100644 --- a/dist/ba_data/python/bastd/actor/spazappearance.py +++ b/dist/ba_data/python/bastd/actor/spazappearance.py @@ -9,10 +9,10 @@ import _ba import ba if TYPE_CHECKING: - from typing import List, Optional, Tuple + from typing import Optional -def get_appearances(include_locked: bool = False) -> List[str]: +def get_appearances(include_locked: bool = False) -> list[str]: """Get the list of available spaz appearances.""" # pylint: disable=too-many-statements # pylint: disable=too-many-branches @@ -104,15 +104,15 @@ class Appearance: self.upper_leg_model = '' self.lower_leg_model = '' self.toes_model = '' - self.jump_sounds: List[str] = [] - self.attack_sounds: List[str] = [] - self.impact_sounds: List[str] = [] - self.death_sounds: List[str] = [] - self.pickup_sounds: List[str] = [] - self.fall_sounds: List[str] = [] + self.jump_sounds: list[str] = [] + self.attack_sounds: list[str] = [] + self.impact_sounds: list[str] = [] + self.death_sounds: list[str] = [] + self.pickup_sounds: list[str] = [] + self.fall_sounds: list[str] = [] self.style = 'spaz' - self.default_color: Optional[Tuple[float, float, float]] = None - self.default_highlight: Optional[Tuple[float, float, float]] = None + self.default_color: Optional[tuple[float, float, float]] = None + self.default_highlight: Optional[tuple[float, float, float]] = None def register_appearances() -> None: diff --git a/dist/ba_data/python/bastd/actor/spazbot.py b/dist/ba_data/python/bastd/actor/spazbot.py index 4e53ee6..9d4d92e 100644 --- a/dist/ba_data/python/bastd/actor/spazbot.py +++ b/dist/ba_data/python/bastd/actor/spazbot.py @@ -13,7 +13,7 @@ import ba from bastd.actor.spaz import Spaz if TYPE_CHECKING: - from typing import Any, Optional, List, Tuple, Sequence, Type, Callable + from typing import Any, Optional, Sequence, Callable from bastd.actor.flag import Flag LITE_BOT_COLOR = (1.2, 0.9, 0.2) @@ -125,7 +125,7 @@ class SpazBot(Spaz): self._map = weakref.ref(activity.map) self.last_player_attacked_by: Optional[ba.Player] = None self.last_attacked_time = 0.0 - self.last_attacked_type: Optional[Tuple[str, str]] = None + self.last_attacked_type: Optional[tuple[str, str]] = None self.target_point_default: Optional[ba.Vec3] = None self.held_count = 0 self.last_player_held_by: Optional[ba.Player] = None @@ -141,7 +141,7 @@ class SpazBot(Spaz): self._throw_release_time: Optional[float] = None self._have_dropped_throw_bomb: Optional[bool] = None - self._player_pts: Optional[List[Tuple[ba.Vec3, ba.Vec3]]] = None + self._player_pts: Optional[list[tuple[ba.Vec3, ba.Vec3]]] = None # These cooldowns didn't exist when these bots were calibrated, # so take them out of the equation. @@ -161,7 +161,7 @@ class SpazBot(Spaz): return mval def _get_target_player_pt( - self) -> Tuple[Optional[ba.Vec3], Optional[ba.Vec3]]: + self) -> tuple[Optional[ba.Vec3], Optional[ba.Vec3]]: """Returns the position and velocity of our target. Both values will be None in the case of no target. @@ -189,7 +189,7 @@ class SpazBot(Spaz): 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: + def set_player_points(self, pts: list[tuple[ba.Vec3, ba.Vec3]]) -> None: """Provide the spaz-bot with the locations of its enemies.""" self._player_pts = pts @@ -882,7 +882,7 @@ class SpazBotSet: self._bot_list_count = 5 self._bot_add_list = 0 self._bot_update_list = 0 - self._bot_lists: List[List[SpazBot]] = [ + self._bot_lists: list[list[SpazBot]] = [ [] for _ in range(self._bot_list_count) ] self._spawn_sound = ba.getsound('spawn') @@ -894,7 +894,7 @@ class SpazBotSet: self.clear() def spawn_bot(self, - bot_type: Type[SpazBot], + bot_type: type[SpazBot], pos: Sequence[float], spawn_time: float = 3.0, on_spawn_call: Callable[[SpazBot], Any] = None) -> None: @@ -907,7 +907,7 @@ class SpazBotSet: on_spawn_call)) self._spawning_count += 1 - def _spawn_bot(self, bot_type: Type[SpazBot], pos: Sequence[float], + def _spawn_bot(self, bot_type: type[SpazBot], pos: Sequence[float], on_spawn_call: Optional[Callable[[SpazBot], Any]]) -> None: spaz = bot_type() ba.playsound(self._spawn_sound, position=pos) @@ -925,9 +925,9 @@ class SpazBotSet: 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]: + def get_living_bots(self) -> list[SpazBot]: """Get the living bots in the set.""" - bots: List[SpazBot] = [] + bots: list[SpazBot] = [] for botlist in self._bot_lists: for bot in botlist: if bot.is_alive(): @@ -977,8 +977,8 @@ class SpazBotSet: if activity is None or activity.expired: return - for i in range(len(self._bot_lists)): - for bot in self._bot_lists[i]: + for i, bot_list in enumerate(self._bot_lists): + for bot in bot_list: bot.handlemessage(ba.DieMessage(immediate=True)) self._bot_lists[i] = [] diff --git a/dist/ba_data/python/bastd/actor/spazfactory.py b/dist/ba_data/python/bastd/actor/spazfactory.py index 7363ec4..ed06d72 100644 --- a/dist/ba_data/python/bastd/actor/spazfactory.py +++ b/dist/ba_data/python/bastd/actor/spazfactory.py @@ -11,7 +11,7 @@ from bastd.gameutils import SharedObjects import _ba if TYPE_CHECKING: - from typing import Any, Dict + from typing import Any class SpazFactory: @@ -206,7 +206,7 @@ class SpazFactory: actions=('modify_node_collision', 'collide', False), ) - self.spaz_media: Dict[str, Any] = {} + self.spaz_media: dict[str, Any] = {} # Lets load some basic rules. # (allows them to be tweaked from the master server) @@ -227,7 +227,7 @@ class SpazFactory: """ return ba.app.spaz_appearances[character].style - def get_media(self, character: str) -> Dict[str, Any]: + def get_media(self, character: str) -> dict[str, Any]: """Return the set of media used by this variant of spaz.""" char = ba.app.spaz_appearances[character] if character not in self.spaz_media: diff --git a/dist/ba_data/python/bastd/actor/text.py b/dist/ba_data/python/bastd/actor/text.py index 96e3be5..7b84d0d 100644 --- a/dist/ba_data/python/bastd/actor/text.py +++ b/dist/ba_data/python/bastd/actor/text.py @@ -10,7 +10,7 @@ from typing import TYPE_CHECKING import ba if TYPE_CHECKING: - from typing import Any, Union, Tuple, Sequence, Optional + from typing import Any, Union, Sequence, Optional class Text(ba.Actor): @@ -50,7 +50,7 @@ class Text(ba.Actor): def __init__(self, text: Union[str, ba.Lstr], - position: Tuple[float, float] = (0.0, 0.0), + 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), diff --git a/dist/ba_data/python/bastd/actor/zoomtext.py b/dist/ba_data/python/bastd/actor/zoomtext.py index 4c0b2c9..5288bb6 100644 --- a/dist/ba_data/python/bastd/actor/zoomtext.py +++ b/dist/ba_data/python/bastd/actor/zoomtext.py @@ -10,7 +10,7 @@ from typing import TYPE_CHECKING import ba if TYPE_CHECKING: - from typing import Any, Union, Tuple, Sequence + from typing import Any, Union, Sequence class ZoomText(ba.Actor): @@ -23,8 +23,8 @@ class ZoomText(ba.Actor): def __init__(self, text: Union[str, ba.Lstr], - position: Tuple[float, float] = (0.0, 0.0), - shiftposition: Tuple[float, float] = None, + position: tuple[float, float] = (0.0, 0.0), + shiftposition: tuple[float, float] = None, shiftdelay: float = None, lifespan: float = None, flash: bool = True, @@ -171,7 +171,7 @@ class ZoomText(ba.Actor): return None return super().handlemessage(msg) - def _jitter(self, position: Tuple[float, float], + def _jitter(self, position: tuple[float, float], jitter_amount: float) -> None: if not self.node: return @@ -187,8 +187,8 @@ class ZoomText(ba.Actor): 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 9ae96c6..6a7d277 100644 --- a/dist/ba_data/python/bastd/appdelegate.py +++ b/dist/ba_data/python/bastd/appdelegate.py @@ -8,15 +8,15 @@ from typing import TYPE_CHECKING import ba if TYPE_CHECKING: - from typing import Type, Any, Dict, Callable, Optional + from typing import Any, Callable, Optional 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: Optional[dict], + self, gameclass: type[ba.GameActivity], + sessiontype: type[ba.Session], settings: Optional[dict], completion_call: Callable[[Optional[dict]], Any]) -> None: """(internal)""" diff --git a/dist/ba_data/python/bastd/game/__pycache__/__init__.cpython-38.pyc b/dist/ba_data/python/bastd/game/__pycache__/__init__.cpython-38.pyc index 9d0d914..56d2597 100644 Binary files a/dist/ba_data/python/bastd/game/__pycache__/__init__.cpython-38.pyc and b/dist/ba_data/python/bastd/game/__pycache__/__init__.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/__init__.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/game/__pycache__/__init__.cpython-39.opt-1.pyc new file mode 100644 index 0000000..de6c0cc Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/__init__.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/__init__.cpython-39.pyc b/dist/ba_data/python/bastd/game/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..634bd92 Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/__init__.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/assault.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/game/__pycache__/assault.cpython-39.opt-1.pyc new file mode 100644 index 0000000..2012152 Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/assault.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/assault.cpython-39.pyc b/dist/ba_data/python/bastd/game/__pycache__/assault.cpython-39.pyc new file mode 100644 index 0000000..817af86 Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/assault.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/capturetheflag.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/game/__pycache__/capturetheflag.cpython-39.opt-1.pyc new file mode 100644 index 0000000..b7903f5 Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/capturetheflag.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/capturetheflag.cpython-39.pyc b/dist/ba_data/python/bastd/game/__pycache__/capturetheflag.cpython-39.pyc new file mode 100644 index 0000000..9a93a61 Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/capturetheflag.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/chosenone.cpython-38.pyc b/dist/ba_data/python/bastd/game/__pycache__/chosenone.cpython-38.pyc index b2ed2e9..5f32323 100644 Binary files a/dist/ba_data/python/bastd/game/__pycache__/chosenone.cpython-38.pyc and b/dist/ba_data/python/bastd/game/__pycache__/chosenone.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/chosenone.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/game/__pycache__/chosenone.cpython-39.opt-1.pyc new file mode 100644 index 0000000..db39a78 Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/chosenone.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/chosenone.cpython-39.pyc b/dist/ba_data/python/bastd/game/__pycache__/chosenone.cpython-39.pyc new file mode 100644 index 0000000..34a9069 Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/chosenone.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/conquest.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/game/__pycache__/conquest.cpython-38.opt-1.pyc index da412db..6714683 100644 Binary files a/dist/ba_data/python/bastd/game/__pycache__/conquest.cpython-38.opt-1.pyc and b/dist/ba_data/python/bastd/game/__pycache__/conquest.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/conquest.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/game/__pycache__/conquest.cpython-39.opt-1.pyc new file mode 100644 index 0000000..285e174 Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/conquest.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/conquest.cpython-39.pyc b/dist/ba_data/python/bastd/game/__pycache__/conquest.cpython-39.pyc new file mode 100644 index 0000000..673f3ca Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/conquest.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/deathmatch.cpython-38.pyc b/dist/ba_data/python/bastd/game/__pycache__/deathmatch.cpython-38.pyc index 7049f9b..8b4f698 100644 Binary files a/dist/ba_data/python/bastd/game/__pycache__/deathmatch.cpython-38.pyc and b/dist/ba_data/python/bastd/game/__pycache__/deathmatch.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/deathmatch.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/game/__pycache__/deathmatch.cpython-39.opt-1.pyc new file mode 100644 index 0000000..03875ce Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/deathmatch.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/deathmatch.cpython-39.pyc b/dist/ba_data/python/bastd/game/__pycache__/deathmatch.cpython-39.pyc new file mode 100644 index 0000000..2024dc0 Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/deathmatch.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/easteregghunt.cpython-38.pyc b/dist/ba_data/python/bastd/game/__pycache__/easteregghunt.cpython-38.pyc index ee0cdad..3dfe33f 100644 Binary files a/dist/ba_data/python/bastd/game/__pycache__/easteregghunt.cpython-38.pyc and b/dist/ba_data/python/bastd/game/__pycache__/easteregghunt.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/easteregghunt.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/game/__pycache__/easteregghunt.cpython-39.opt-1.pyc new file mode 100644 index 0000000..f32991c Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/easteregghunt.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/easteregghunt.cpython-39.pyc b/dist/ba_data/python/bastd/game/__pycache__/easteregghunt.cpython-39.pyc new file mode 100644 index 0000000..50e200a Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/easteregghunt.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/elimination.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/game/__pycache__/elimination.cpython-38.opt-1.pyc index b9067f2..7a3a037 100644 Binary files a/dist/ba_data/python/bastd/game/__pycache__/elimination.cpython-38.opt-1.pyc and b/dist/ba_data/python/bastd/game/__pycache__/elimination.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/elimination.cpython-38.pyc b/dist/ba_data/python/bastd/game/__pycache__/elimination.cpython-38.pyc index fb71440..19b8064 100644 Binary files a/dist/ba_data/python/bastd/game/__pycache__/elimination.cpython-38.pyc and b/dist/ba_data/python/bastd/game/__pycache__/elimination.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/elimination.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/game/__pycache__/elimination.cpython-39.opt-1.pyc new file mode 100644 index 0000000..112c264 Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/elimination.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/elimination.cpython-39.pyc b/dist/ba_data/python/bastd/game/__pycache__/elimination.cpython-39.pyc new file mode 100644 index 0000000..b0858e6 Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/elimination.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/football.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/game/__pycache__/football.cpython-38.opt-1.pyc index 6dcda47..d47186c 100644 Binary files a/dist/ba_data/python/bastd/game/__pycache__/football.cpython-38.opt-1.pyc and b/dist/ba_data/python/bastd/game/__pycache__/football.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/football.cpython-38.pyc b/dist/ba_data/python/bastd/game/__pycache__/football.cpython-38.pyc index 4486431..7d58a84 100644 Binary files a/dist/ba_data/python/bastd/game/__pycache__/football.cpython-38.pyc and b/dist/ba_data/python/bastd/game/__pycache__/football.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/football.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/game/__pycache__/football.cpython-39.opt-1.pyc new file mode 100644 index 0000000..8eb733b Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/football.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/football.cpython-39.pyc b/dist/ba_data/python/bastd/game/__pycache__/football.cpython-39.pyc new file mode 100644 index 0000000..6a9caf6 Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/football.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/hockey.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/game/__pycache__/hockey.cpython-38.opt-1.pyc index 276db75..74de699 100644 Binary files a/dist/ba_data/python/bastd/game/__pycache__/hockey.cpython-38.opt-1.pyc and b/dist/ba_data/python/bastd/game/__pycache__/hockey.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/hockey.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/game/__pycache__/hockey.cpython-39.opt-1.pyc new file mode 100644 index 0000000..c99d353 Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/hockey.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/hockey.cpython-39.pyc b/dist/ba_data/python/bastd/game/__pycache__/hockey.cpython-39.pyc new file mode 100644 index 0000000..c5c4854 Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/hockey.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/keepaway.cpython-38.pyc b/dist/ba_data/python/bastd/game/__pycache__/keepaway.cpython-38.pyc index 014233f..04d407f 100644 Binary files a/dist/ba_data/python/bastd/game/__pycache__/keepaway.cpython-38.pyc and b/dist/ba_data/python/bastd/game/__pycache__/keepaway.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/keepaway.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/game/__pycache__/keepaway.cpython-39.opt-1.pyc new file mode 100644 index 0000000..24ddc91 Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/keepaway.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/keepaway.cpython-39.pyc b/dist/ba_data/python/bastd/game/__pycache__/keepaway.cpython-39.pyc new file mode 100644 index 0000000..2a82df3 Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/keepaway.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/kingofthehill.cpython-38.pyc b/dist/ba_data/python/bastd/game/__pycache__/kingofthehill.cpython-38.pyc index 952dd85..d1fb27a 100644 Binary files a/dist/ba_data/python/bastd/game/__pycache__/kingofthehill.cpython-38.pyc and b/dist/ba_data/python/bastd/game/__pycache__/kingofthehill.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/kingofthehill.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/game/__pycache__/kingofthehill.cpython-39.opt-1.pyc new file mode 100644 index 0000000..8ad143a Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/kingofthehill.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/kingofthehill.cpython-39.pyc b/dist/ba_data/python/bastd/game/__pycache__/kingofthehill.cpython-39.pyc new file mode 100644 index 0000000..5726973 Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/kingofthehill.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/meteorshower.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/game/__pycache__/meteorshower.cpython-38.opt-1.pyc index 353d53c..42c778d 100644 Binary files a/dist/ba_data/python/bastd/game/__pycache__/meteorshower.cpython-38.opt-1.pyc and b/dist/ba_data/python/bastd/game/__pycache__/meteorshower.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/meteorshower.cpython-38.pyc b/dist/ba_data/python/bastd/game/__pycache__/meteorshower.cpython-38.pyc index 31799fa..99cf477 100644 Binary files a/dist/ba_data/python/bastd/game/__pycache__/meteorshower.cpython-38.pyc and b/dist/ba_data/python/bastd/game/__pycache__/meteorshower.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/meteorshower.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/game/__pycache__/meteorshower.cpython-39.opt-1.pyc new file mode 100644 index 0000000..a2b6654 Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/meteorshower.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/meteorshower.cpython-39.pyc b/dist/ba_data/python/bastd/game/__pycache__/meteorshower.cpython-39.pyc new file mode 100644 index 0000000..8437fd9 Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/meteorshower.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/ninjafight.cpython-38.pyc b/dist/ba_data/python/bastd/game/__pycache__/ninjafight.cpython-38.pyc index f7e1624..f5ca095 100644 Binary files a/dist/ba_data/python/bastd/game/__pycache__/ninjafight.cpython-38.pyc and b/dist/ba_data/python/bastd/game/__pycache__/ninjafight.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/ninjafight.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/game/__pycache__/ninjafight.cpython-39.opt-1.pyc new file mode 100644 index 0000000..3e49b0f Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/ninjafight.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/ninjafight.cpython-39.pyc b/dist/ba_data/python/bastd/game/__pycache__/ninjafight.cpython-39.pyc new file mode 100644 index 0000000..e618e6c Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/ninjafight.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/onslaught.cpython-38.pyc b/dist/ba_data/python/bastd/game/__pycache__/onslaught.cpython-38.pyc index f0de6b9..d2b7bc0 100644 Binary files a/dist/ba_data/python/bastd/game/__pycache__/onslaught.cpython-38.pyc and b/dist/ba_data/python/bastd/game/__pycache__/onslaught.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/onslaught.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/game/__pycache__/onslaught.cpython-39.opt-1.pyc new file mode 100644 index 0000000..45c93b3 Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/onslaught.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/onslaught.cpython-39.pyc b/dist/ba_data/python/bastd/game/__pycache__/onslaught.cpython-39.pyc new file mode 100644 index 0000000..5dd5402 Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/onslaught.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/race.cpython-38.pyc b/dist/ba_data/python/bastd/game/__pycache__/race.cpython-38.pyc index f2e9964..3404751 100644 Binary files a/dist/ba_data/python/bastd/game/__pycache__/race.cpython-38.pyc and b/dist/ba_data/python/bastd/game/__pycache__/race.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/race.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/game/__pycache__/race.cpython-39.opt-1.pyc new file mode 100644 index 0000000..3fc3775 Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/race.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/race.cpython-39.pyc b/dist/ba_data/python/bastd/game/__pycache__/race.cpython-39.pyc new file mode 100644 index 0000000..c188ac7 Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/race.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/runaround.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/game/__pycache__/runaround.cpython-38.opt-1.pyc index adcd08d..27c7380 100644 Binary files a/dist/ba_data/python/bastd/game/__pycache__/runaround.cpython-38.opt-1.pyc and b/dist/ba_data/python/bastd/game/__pycache__/runaround.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/runaround.cpython-38.pyc b/dist/ba_data/python/bastd/game/__pycache__/runaround.cpython-38.pyc index efbd157..cbc5f9d 100644 Binary files a/dist/ba_data/python/bastd/game/__pycache__/runaround.cpython-38.pyc and b/dist/ba_data/python/bastd/game/__pycache__/runaround.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/runaround.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/game/__pycache__/runaround.cpython-39.opt-1.pyc new file mode 100644 index 0000000..6361d6e Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/runaround.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/runaround.cpython-39.pyc b/dist/ba_data/python/bastd/game/__pycache__/runaround.cpython-39.pyc new file mode 100644 index 0000000..841f830 Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/runaround.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/targetpractice.cpython-38.pyc b/dist/ba_data/python/bastd/game/__pycache__/targetpractice.cpython-38.pyc index 3069a6f..f6ead4b 100644 Binary files a/dist/ba_data/python/bastd/game/__pycache__/targetpractice.cpython-38.pyc and b/dist/ba_data/python/bastd/game/__pycache__/targetpractice.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/targetpractice.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/game/__pycache__/targetpractice.cpython-39.opt-1.pyc new file mode 100644 index 0000000..d5d6b11 Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/targetpractice.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/targetpractice.cpython-39.pyc b/dist/ba_data/python/bastd/game/__pycache__/targetpractice.cpython-39.pyc new file mode 100644 index 0000000..93ce1fc Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/targetpractice.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/thelaststand.cpython-38.pyc b/dist/ba_data/python/bastd/game/__pycache__/thelaststand.cpython-38.pyc index 5321910..093bb0c 100644 Binary files a/dist/ba_data/python/bastd/game/__pycache__/thelaststand.cpython-38.pyc and b/dist/ba_data/python/bastd/game/__pycache__/thelaststand.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/thelaststand.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/game/__pycache__/thelaststand.cpython-39.opt-1.pyc new file mode 100644 index 0000000..d2d1295 Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/thelaststand.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/game/__pycache__/thelaststand.cpython-39.pyc b/dist/ba_data/python/bastd/game/__pycache__/thelaststand.cpython-39.pyc new file mode 100644 index 0000000..4e5da77 Binary files /dev/null and b/dist/ba_data/python/bastd/game/__pycache__/thelaststand.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/game/assault.py b/dist/ba_data/python/bastd/game/assault.py index 44dc8ca..6389b4b 100644 --- a/dist/ba_data/python/bastd/game/assault.py +++ b/dist/ba_data/python/bastd/game/assault.py @@ -17,7 +17,7 @@ from bastd.actor.scoreboard import Scoreboard from bastd.gameutils import SharedObjects if TYPE_CHECKING: - from typing import Any, Type, List, Dict, Sequence, Union + from typing import Any, Sequence, Union class Player(ba.Player['Team']): @@ -72,11 +72,11 @@ class AssaultGame(ba.TeamGameActivity[Player, Team]): ] @classmethod - def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool: + def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool: return issubclass(sessiontype, ba.DualTeamSession) @classmethod - def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]: + def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]: return ba.getmaps('team_flag') def __init__(self, settings: dict): @@ -84,7 +84,7 @@ class AssaultGame(ba.TeamGameActivity[Player, Team]): self._scoreboard = Scoreboard() self._last_score_time = 0.0 self._score_sound = ba.getsound('score') - self._base_region_materials: Dict[int, ba.Material] = {} + self._base_region_materials: dict[int, ba.Material] = {} self._epic_mode = bool(settings['Epic Mode']) self._score_to_win = int(settings['Score to Win']) self._time_limit = float(settings['Time Limit']) diff --git a/dist/ba_data/python/bastd/game/capturetheflag.py b/dist/ba_data/python/bastd/game/capturetheflag.py index 638d2d3..0e91062 100644 --- a/dist/ba_data/python/bastd/game/capturetheflag.py +++ b/dist/ba_data/python/bastd/game/capturetheflag.py @@ -16,7 +16,7 @@ from bastd.actor.flag import (FlagFactory, Flag, FlagPickedUpMessage, FlagDroppedMessage, FlagDiedMessage) if TYPE_CHECKING: - from typing import Any, Type, List, Dict, Sequence, Union, Optional + from typing import Any, Sequence, Union, Optional class CTFFlag(Flag): @@ -132,11 +132,11 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): ] @classmethod - def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool: + def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool: return issubclass(sessiontype, ba.DualTeamSession) @classmethod - def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]: + def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]: return ba.getmaps('team_flag') def __init__(self, settings: dict): @@ -491,10 +491,10 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): player = spaz.getplayer(Player, True) team: Team = player.team player.touching_own_flag = 0 - no_physical_mats: List[ba.Material] = [ + no_physical_mats: list[ba.Material] = [ team.spaz_material_no_flag_physical ] - no_collide_mats: List[ba.Material] = [ + no_collide_mats: list[ba.Material] = [ team.spaz_material_no_flag_collide ] diff --git a/dist/ba_data/python/bastd/game/chosenone.py b/dist/ba_data/python/bastd/game/chosenone.py index 465dec9..9f5e331 100644 --- a/dist/ba_data/python/bastd/game/chosenone.py +++ b/dist/ba_data/python/bastd/game/chosenone.py @@ -16,7 +16,7 @@ from bastd.actor.scoreboard import Scoreboard from bastd.gameutils import SharedObjects if TYPE_CHECKING: - from typing import Any, Type, List, Dict, Optional, Sequence, Union + from typing import Any, Optional, Sequence, Union class Player(ba.Player['Team']): @@ -81,7 +81,7 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]): scoreconfig = ba.ScoreConfig(label='Time Held') @classmethod - def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]: + def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]: return ba.getmaps('keep_away') def __init__(self, settings: dict): @@ -89,7 +89,7 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]): self._scoreboard = Scoreboard() self._chosen_one_player: Optional[Player] = None self._swipsound = ba.getsound('swip') - self._countdownsounds: Dict[int, ba.Sound] = { + self._countdownsounds: dict[int, ba.Sound] = { 10: ba.getsound('announceTen'), 9: ba.getsound('announceNine'), 8: ba.getsound('announceEight'), diff --git a/dist/ba_data/python/bastd/game/conquest.py b/dist/ba_data/python/bastd/game/conquest.py index b2404b8..93bb279 100644 --- a/dist/ba_data/python/bastd/game/conquest.py +++ b/dist/ba_data/python/bastd/game/conquest.py @@ -17,7 +17,7 @@ from bastd.actor.playerspaz import PlayerSpaz from bastd.gameutils import SharedObjects if TYPE_CHECKING: - from typing import Any, Optional, Type, List, Dict, Sequence, Union + from typing import Any, Optional, Sequence, Union from bastd.actor.respawnicon import RespawnIcon @@ -105,11 +105,11 @@ class ConquestGame(ba.TeamGameActivity[Player, Team]): ] @classmethod - def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool: + def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool: return issubclass(sessiontype, ba.DualTeamSession) @classmethod - def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]: + def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]: return ba.getmaps('conquest') def __init__(self, settings: dict): @@ -119,7 +119,7 @@ class ConquestGame(ba.TeamGameActivity[Player, Team]): self._score_sound = ba.getsound('score') self._swipsound = ba.getsound('swip') self._extraflagmat = ba.Material() - self._flags: List[ConquestFlag] = [] + self._flags: list[ConquestFlag] = [] self._epic_mode = bool(settings['Epic Mode']) self._time_limit = float(settings['Time Limit']) @@ -159,8 +159,8 @@ class ConquestGame(ba.TeamGameActivity[Player, Team]): self.setup_standard_powerup_drops() # Set up flags with marker lights. - for i in range(len(self.map.flag_points)): - point = self.map.flag_points[i] + for i, flag_point in enumerate(self.map.flag_points): + point = flag_point flag = ConquestFlag(position=point, touchable=False, materials=[self._extraflagmat]) @@ -177,14 +177,14 @@ class ConquestGame(ba.TeamGameActivity[Player, Team]): }) # Give teams a flag to start with. - for i in range(len(self.teams)): - self._flags[i].team = self.teams[i] + for i, team in enumerate(self.teams): + self._flags[i].team = team light = self._flags[i].light assert light node = self._flags[i].node assert node - light.color = self.teams[i].color - node.color = self.teams[i].color + light.color = team.color + node.color = team.color self._update_scores() diff --git a/dist/ba_data/python/bastd/game/deathmatch.py b/dist/ba_data/python/bastd/game/deathmatch.py index f91c7af..ddfa0d4 100644 --- a/dist/ba_data/python/bastd/game/deathmatch.py +++ b/dist/ba_data/python/bastd/game/deathmatch.py @@ -14,7 +14,7 @@ from bastd.actor.playerspaz import PlayerSpaz from bastd.actor.scoreboard import Scoreboard if TYPE_CHECKING: - from typing import Any, Type, List, Dict, Tuple, Union, Sequence, Optional + from typing import Any, Union, Sequence, Optional class Player(ba.Player['Team']): @@ -40,7 +40,7 @@ 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', @@ -86,12 +86,12 @@ class DeathMatchGame(ba.TeamGameActivity[Player, Team]): return settings @classmethod - def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool: + def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool: return (issubclass(sessiontype, ba.DualTeamSession) or issubclass(sessiontype, ba.FreeForAllSession)) @classmethod - def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]: + def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]: return ba.getmaps('melee') def __init__(self, settings: dict): diff --git a/dist/ba_data/python/bastd/game/easteregghunt.py b/dist/ba_data/python/bastd/game/easteregghunt.py index 2c5b9b0..d7f3655 100644 --- a/dist/ba_data/python/bastd/game/easteregghunt.py +++ b/dist/ba_data/python/bastd/game/easteregghunt.py @@ -20,7 +20,7 @@ from bastd.actor.respawnicon import RespawnIcon from bastd.gameutils import SharedObjects if TYPE_CHECKING: - from typing import Any, Type, Dict, List, Tuple, Optional + from typing import Any, Optional class Player(ba.Player['Team']): @@ -49,12 +49,12 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]): # We're currently hard-coded for one map. @classmethod - def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]: + def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]: return ['Tower D'] # We support teams, free-for-all, and co-op sessions. @classmethod - def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool: + 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)) @@ -75,7 +75,7 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]): self.egg_material.add_actions( conditions=('they_have_material', shared.player_material), actions=(('call', 'at_connect', self._on_egg_player_collide), )) - self._eggs: List[Egg] = [] + self._eggs: list[Egg] = [] self._update_timer: Optional[ba.Timer] = None self._countdown: Optional[OnScreenCountdown] = None self._bots: Optional[SpazBotSet] = None @@ -224,7 +224,7 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]): class Egg(ba.Actor): """A lovely egg that can be picked up for points.""" - def __init__(self, position: Tuple[float, float, float] = (0.0, 1.0, 0.0)): + def __init__(self, position: tuple[float, float, float] = (0.0, 1.0, 0.0)): super().__init__() activity = self.activity assert isinstance(activity, EasterEggHuntGame) diff --git a/dist/ba_data/python/bastd/game/elimination.py b/dist/ba_data/python/bastd/game/elimination.py index aac7d82..a0df770 100644 --- a/dist/ba_data/python/bastd/game/elimination.py +++ b/dist/ba_data/python/bastd/game/elimination.py @@ -14,8 +14,7 @@ from bastd.actor.spazfactory import SpazFactory from bastd.actor.scoreboard import Scoreboard if TYPE_CHECKING: - from typing import (Any, Tuple, Dict, Type, List, Sequence, Optional, - Union) + from typing import Any, Sequence, Optional, Union class Icon(ba.Actor): @@ -23,7 +22,7 @@ class Icon(ba.Actor): def __init__(self, player: Player, - position: Tuple[float, float], + position: tuple[float, float], scale: float, show_lives: bool = True, show_death: bool = True, @@ -83,7 +82,7 @@ class Icon(ba.Actor): }) self.set_position_and_scale(position, scale) - def set_position_and_scale(self, position: Tuple[float, float], + def set_position_and_scale(self, position: tuple[float, float], scale: float) -> None: """(Re)position the icon.""" assert self.node @@ -156,7 +155,7 @@ class Player(ba.Player['Team']): def __init__(self) -> None: self.lives = 0 - self.icons: List[Icon] = [] + self.icons: list[Icon] = [] class Team(ba.Team[Player]): @@ -164,7 +163,7 @@ class Team(ba.Team[Player]): def __init__(self) -> None: self.survival_seconds: Optional[int] = None - self.spawn_order: List[Player] = [] + self.spawn_order: list[Player] = [] # ba_meta export game @@ -179,9 +178,11 @@ class EliminationGame(ba.TeamGameActivity[Player, Team]): # Show messages when players die since it's meaningful here. announce_player_deaths = True + allow_mid_activity_joins = False + @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', @@ -222,12 +223,12 @@ class EliminationGame(ba.TeamGameActivity[Player, Team]): return settings @classmethod - def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool: + def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool: return (issubclass(sessiontype, ba.DualTeamSession) or issubclass(sessiontype, ba.FreeForAllSession)) @classmethod - def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]: + def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]: return ba.getmaps('melee') def __init__(self, settings: dict): @@ -257,23 +258,6 @@ class EliminationGame(ba.TeamGameActivity[Player, Team]): self.session, ba.DualTeamSession) else 'last one standing wins' def on_player_join(self, player: Player) -> None: - - # No longer allowing mid-game joiners here; too easy to exploit. - if self.has_begun(): - - # Make sure their team has survival seconds set if they're all dead - # (otherwise blocked new ffa players are considered 'still alive' - # in score tallying). - if (self._get_total_team_lives(player.team) == 0 - and player.team.survival_seconds is None): - player.team.survival_seconds = 0 - ba.screenmessage( - ba.Lstr(resource='playerDelayedJoinText', - subs=[('${PLAYER}', player.getname(full=True))]), - color=(0, 1, 0), - ) - return - player.lives = self._lives_per_player if self._solo_mode: @@ -436,7 +420,7 @@ class EliminationGame(ba.TeamGameActivity[Player, Team]): if living_player: assert living_player_pos is not None player_pos = ba.Vec3(living_player_pos) - points: List[Tuple[float, ba.Vec3]] = [] + points: list[tuple[float, ba.Vec3]] = [] for team in self.teams: start_pos = ba.Vec3(self.map.get_start_position(team.id)) points.append( @@ -554,7 +538,7 @@ class EliminationGame(ba.TeamGameActivity[Player, Team]): if len(self._get_living_teams()) < 2: self._round_end_timer = ba.Timer(0.5, self.end_game) - def _get_living_teams(self) -> List[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 diff --git a/dist/ba_data/python/bastd/game/football.py b/dist/ba_data/python/bastd/game/football.py index 3531dfc..bcddc8b 100644 --- a/dist/ba_data/python/bastd/game/football.py +++ b/dist/ba_data/python/bastd/game/football.py @@ -26,7 +26,7 @@ from bastd.actor.spazbot import (SpazBotDiedMessage, SpazBotPunchedMessage, StickyBot, ExplodeyBot) if TYPE_CHECKING: - from typing import Any, List, Type, Dict, Sequence, Optional, Union + from typing import Any, Sequence, Optional, Union from bastd.actor.spaz import Spaz from bastd.actor.spazbot import SpazBot @@ -110,12 +110,12 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]): default_music = ba.MusicType.FOOTBALL @classmethod - def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool: + def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool: # We only support two-team play. return issubclass(sessiontype, ba.DualTeamSession) @classmethod - def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]: + def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]: return ba.getmaps('football') def __init__(self, settings: dict): @@ -137,7 +137,7 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]): ('call', 'at_connect', self._handle_score), )) self._flag_spawn_pos: Optional[Sequence[float]] = None - self._score_regions: List[ba.NodeActor] = [] + self._score_regions: list[ba.NodeActor] = [] self._flag: Optional[FootballFlag] = None self._flag_respawn_timer: Optional[ba.Timer] = None self._flag_respawn_light: Optional[ba.NodeActor] = None @@ -206,8 +206,8 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]): return region = ba.getcollision().sourcenode i = None - for i in range(len(self._score_regions)): - if region == self._score_regions[i].node: + for i, score_region in enumerate(self._score_regions): + if region == score_region.node: break for team in self.teams: if team.id == i: @@ -377,12 +377,12 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): self._player_has_punched = False self._scoreboard: Optional[Scoreboard] = None self._flag_spawn_pos: Optional[Sequence[float]] = None - self._score_regions: List[ba.NodeActor] = [] - self._exclude_powerups: List[str] = [] + self._score_regions: list[ba.NodeActor] = [] + self._exclude_powerups: list[str] = [] self._have_tnt = False - self._bot_types_initial: Optional[List[Type[SpazBot]]] = None - self._bot_types_7: Optional[List[Type[SpazBot]]] = None - self._bot_types_14: Optional[List[Type[SpazBot]]] = None + self._bot_types_initial: Optional[list[type[SpazBot]]] = None + self._bot_types_7: Optional[list[type[SpazBot]]] = None + self._bot_types_14: Optional[list[type[SpazBot]]] = None self._bot_team: Optional[Team] = None self._starttime_ms: Optional[int] = None self._time_text: Optional[ba.NodeActor] = None @@ -436,9 +436,9 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): controlsguide.ControlsGuide(delay=3.0, lifespan=10.0, bright=True).autoretain() assert self.initialplayerinfos is not None - abot: Type[SpazBot] - bbot: Type[SpazBot] - cbot: Type[SpazBot] + abot: type[SpazBot] + bbot: type[SpazBot] + cbot: type[SpazBot] if self._preset in ['rookie', 'rookie_easy']: self._exclude_powerups = ['curse'] self._have_tnt = False @@ -467,7 +467,7 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): self._bot_types_initial = [ChargerBot] * len( self.initialplayerinfos) abot = (BrawlerBot if self._preset == 'pro' else BrawlerBotLite) - typed_bot_list: List[Type[SpazBot]] = [] + typed_bot_list: list[type[SpazBot]] = [] self._bot_types_7 = ( typed_bot_list + [abot] + [BomberBot] * (1 if len(self.initialplayerinfos) < 3 else 2)) @@ -479,7 +479,7 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): self._have_tnt = True abot = (BrawlerBotPro if self._preset == 'uber' else BrawlerBot) bbot = (TriggerBotPro if self._preset == 'uber' else TriggerBot) - typed_bot_list_2: List[Type[SpazBot]] = [] + typed_bot_list_2: list[type[SpazBot]] = [] self._bot_types_initial = (typed_bot_list_2 + [StickyBot] + [abot] * len(self.initialplayerinfos)) self._bot_types_7 = ( @@ -542,7 +542,7 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): for bottype in self._bot_types_initial: self._spawn_bot(bottype) - def _on_got_scores_to_beat(self, scores: List[Dict[str, Any]]) -> None: + def _on_got_scores_to_beat(self, scores: list[dict[str, Any]]) -> None: self._show_standard_scores_to_beat_ui(scores) def _on_bot_spawn(self, spaz: SpazBot) -> None: @@ -550,7 +550,7 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): spaz.target_point_default = ba.Vec3(0, 0, 0) def _spawn_bot(self, - spaz_type: Type[SpazBot], + 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) @@ -651,8 +651,8 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): # See which score region it was. region = ba.getcollision().sourcenode i = None - for i in range(len(self._score_regions)): - if region == self._score_regions[i].node: + for i, score_region in enumerate(self._score_regions): + if region == score_region.node: break for team in [self.teams[0], self._bot_team]: diff --git a/dist/ba_data/python/bastd/game/hockey.py b/dist/ba_data/python/bastd/game/hockey.py index 3964ad7..7f47bf7 100644 --- a/dist/ba_data/python/bastd/game/hockey.py +++ b/dist/ba_data/python/bastd/game/hockey.py @@ -16,7 +16,7 @@ from bastd.actor.powerupbox import PowerupBoxFactory from bastd.gameutils import SharedObjects if TYPE_CHECKING: - from typing import Any, Sequence, Dict, Type, List, Optional, Union + from typing import Any, Sequence, Optional, Union class PuckDiedMessage: @@ -36,7 +36,7 @@ class Puck(ba.Actor): # Spawn just above the provided point. self._spawn_pos = (position[0], position[1] + 1.0, position[2]) - self.last_players_to_touch: Dict[int, Player] = {} + self.last_players_to_touch: dict[int, Player] = {} self.scored = False assert activity is not None assert isinstance(activity, HockeyGame) @@ -141,11 +141,11 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]): default_music = ba.MusicType.HOCKEY @classmethod - def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool: + def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool: return issubclass(sessiontype, ba.DualTeamSession) @classmethod - def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]: + def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]: return ba.getmaps('hockey') def __init__(self, settings: dict): @@ -199,7 +199,7 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]): True), ('modify_part_collision', 'physical', False), ('call', 'at_connect', self._handle_score))) self._puck_spawn_pos: Optional[Sequence[float]] = None - self._score_regions: Optional[List[ba.NodeActor]] = None + self._score_regions: Optional[list[ba.NodeActor]] = None self._puck: Optional[Puck] = None self._score_to_win = int(settings['Score to Win']) self._time_limit = float(settings['Time Limit']) @@ -277,8 +277,8 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]): region = ba.getcollision().sourcenode index = 0 - for index in range(len(self._score_regions)): - if region == self._score_regions[index].node: + for index, score_region in enumerate(self._score_regions): + if region == score_region.node: break for team in self.teams: diff --git a/dist/ba_data/python/bastd/game/keepaway.py b/dist/ba_data/python/bastd/game/keepaway.py index 7810b2e..263326a 100644 --- a/dist/ba_data/python/bastd/game/keepaway.py +++ b/dist/ba_data/python/bastd/game/keepaway.py @@ -17,7 +17,7 @@ from bastd.actor.flag import (Flag, FlagDroppedMessage, FlagDiedMessage, FlagPickedUpMessage) if TYPE_CHECKING: - from typing import Any, Type, List, Dict, Optional, Sequence, Union + from typing import Any, Optional, Sequence, Union class FlagState(Enum): @@ -81,12 +81,12 @@ class KeepAwayGame(ba.TeamGameActivity[Player, Team]): default_music = ba.MusicType.KEEP_AWAY @classmethod - def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool: + def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool: return (issubclass(sessiontype, ba.DualTeamSession) or issubclass(sessiontype, ba.FreeForAllSession)) @classmethod - def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]: + def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]: return ba.getmaps('keep_away') def __init__(self, settings: dict): @@ -108,7 +108,7 @@ class KeepAwayGame(ba.TeamGameActivity[Player, Team]): } self._flag_spawn_pos: Optional[Sequence[float]] = None self._update_timer: Optional[ba.Timer] = None - self._holding_players: List[Player] = [] + self._holding_players: list[Player] = [] self._flag_state: Optional[FlagState] = None self._flag_light: Optional[ba.Node] = None self._scoring_team: Optional[Team] = None diff --git a/dist/ba_data/python/bastd/game/kingofthehill.py b/dist/ba_data/python/bastd/game/kingofthehill.py index 6672626..9194f5a 100644 --- a/dist/ba_data/python/bastd/game/kingofthehill.py +++ b/dist/ba_data/python/bastd/game/kingofthehill.py @@ -18,8 +18,7 @@ from bastd.actor.scoreboard import Scoreboard from bastd.gameutils import SharedObjects if TYPE_CHECKING: - from weakref import ReferenceType - from typing import Any, Type, List, Dict, Optional, Sequence, Union + from typing import Any, Optional, Sequence, Union class FlagState(Enum): @@ -84,11 +83,11 @@ class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]): scoreconfig = ba.ScoreConfig(label='Time Held') @classmethod - def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool: + def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool: return issubclass(sessiontype, ba.MultiTeamSession) @classmethod - def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]: + def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]: return ba.getmaps('king_of_the_hill') def __init__(self, settings: dict): @@ -113,7 +112,7 @@ class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]): self._flag_state: Optional[FlagState] = None self._flag: Optional[Flag] = None self._flag_light: Optional[ba.Node] = None - self._scoring_team: Optional[ReferenceType[Team]] = None + self._scoring_team: Optional[weakref.ref[Team]] = None self._hold_time = int(settings['Hold Time']) self._time_limit = float(settings['Time Limit']) self._flag_region_material = ba.Material() diff --git a/dist/ba_data/python/bastd/game/meteorshower.py b/dist/ba_data/python/bastd/game/meteorshower.py index 9be7990..e346160 100644 --- a/dist/ba_data/python/bastd/game/meteorshower.py +++ b/dist/ba_data/python/bastd/game/meteorshower.py @@ -15,7 +15,7 @@ from bastd.actor.bomb import Bomb from bastd.actor.onscreentimer import OnScreenTimer if TYPE_CHECKING: - from typing import Any, Sequence, Optional, List, Dict, Type, Type + from typing import Any, Sequence, Optional class Player(ba.Player['Team']): @@ -44,14 +44,18 @@ class MeteorShowerGame(ba.TeamGameActivity[Player, Team]): # Print messages when players die (since its meaningful in this game). announce_player_deaths = True - # we're currently hard-coded for one map.. + # Don't allow joining after we start + # (would enable leave/rejoin tomfoolery). + allow_mid_activity_joins = False + + # We're currently hard-coded for one map. @classmethod - def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]: + def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]: return ['Rampage'] # We support teams, free-for-all, and co-op sessions. @classmethod - def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool: + 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)) @@ -93,22 +97,6 @@ class MeteorShowerGame(ba.TeamGameActivity[Player, Team]): # Check for immediate end (if we've only got 1 player, etc). ba.timer(5.0, self._check_end_game) - def on_player_join(self, player: Player) -> None: - # Don't allow joining after we start - # (would enable leave/rejoin tomfoolery). - if self.has_begun(): - ba.screenmessage( - ba.Lstr(resource='playerDelayedJoinText', - subs=[('${PLAYER}', player.getname(full=True))]), - color=(0, 1, 0), - ) - # For score purposes, mark them as having died right as the - # game started. - assert self._timer is not None - player.death_time = self._timer.getstarttime() - return - self.spawn_player(player) - def on_player_leave(self, player: Player) -> None: # Augment default behavior. super().on_player_leave(player) diff --git a/dist/ba_data/python/bastd/game/ninjafight.py b/dist/ba_data/python/bastd/game/ninjafight.py index 37ba021..13b6341 100644 --- a/dist/ba_data/python/bastd/game/ninjafight.py +++ b/dist/ba_data/python/bastd/game/ninjafight.py @@ -15,7 +15,7 @@ from bastd.actor.spazbot import SpazBotSet, ChargerBot, SpazBotDiedMessage from bastd.actor.onscreentimer import OnScreenTimer if TYPE_CHECKING: - from typing import Any, Type, Dict, List, Optional + from typing import Any, Optional class Player(ba.Player['Team']): @@ -41,14 +41,14 @@ class NinjaFightGame(ba.TeamGameActivity[Player, Team]): default_music = ba.MusicType.TO_THE_DEATH @classmethod - def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]: + def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]: # For now we're hard-coding spawn positions and whatnot # so we need to be sure to specify that we only support # a specific map. return ['Courtyard'] @classmethod - def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool: + def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool: # We currently support Co-Op only. return issubclass(sessiontype, ba.CoopSession) diff --git a/dist/ba_data/python/bastd/game/onslaught.py b/dist/ba_data/python/bastd/game/onslaught.py index 9b8efe8..d56f3fa 100644 --- a/dist/ba_data/python/bastd/game/onslaught.py +++ b/dist/ba_data/python/bastd/game/onslaught.py @@ -28,21 +28,21 @@ from bastd.actor.spazbot import ( TriggerBotProShielded, BrawlerBotPro, BomberBotProShielded) if TYPE_CHECKING: - from typing import Any, Type, Dict, Optional, List, Tuple, Union, Sequence + from typing import Any, Optional, Union, Sequence from bastd.actor.spazbot import SpazBot @dataclass class Wave: """A wave of enemies.""" - entries: List[Union[Spawn, Spacing, Delay, None]] + entries: list[Union[Spawn, Spacing, Delay, None]] base_angle: float = 0.0 @dataclass class Spawn: """A bot spawn event in a wave.""" - bottype: Union[Type[SpazBot], str] + bottype: Union[type[SpazBot], str] point: Optional[Point] = None spacing: float = 5.0 @@ -123,7 +123,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): name = 'Onslaught' description = 'Defeat all enemies.' - tips: List[Union[str, ba.GameTip]] = [ + tips: list[Union[str, ba.GameTip]] = [ 'Hold any button to run.' ' (Trigger buttons work well if you have them)', 'Try tricking enemies into killing eachother or running off cliffs.', @@ -179,8 +179,8 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): self._dingsound = ba.getsound('dingSmall') self._dingsoundhigh = ba.getsound('dingSmallHigh') self._have_tnt = False - self._excluded_powerups: Optional[List[str]] = None - self._waves: List[Wave] = [] + self._excluded_powerups: Optional[list[str]] = None + self._waves: list[Wave] = [] self._tntspawner: Optional[TNTSpawner] = None self._bots: Optional[SpazBotSet] = None self._powerup_drop_timer: Optional[ba.Timer] = None @@ -538,10 +538,10 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): self._bots = SpazBotSet() ba.timer(4.0, self._start_updating_waves) - def _on_got_scores_to_beat(self, scores: List[Dict[str, Any]]) -> None: + def _on_got_scores_to_beat(self, scores: list[dict[str, Any]]) -> None: self._show_standard_scores_to_beat_ui(scores) - def _get_dist_grp_totals(self, grps: List[Any]) -> Tuple[int, int]: + def _get_dist_grp_totals(self, grps: list[Any]) -> tuple[int, int]: totalpts = 0 totaldudes = 0 for grp in grps: @@ -553,11 +553,11 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): def _get_distribution(self, target_points: int, min_dudes: int, max_dudes: int, group_count: int, - max_level: int) -> List[List[Tuple[int, int]]]: + max_level: int) -> list[list[tuple[int, int]]]: """Calculate a distribution of bad guys given some params.""" max_iterations = 10 + max_dudes * 2 - groups: List[List[Tuple[int, int]]] = [] + groups: list[list[tuple[int, int]]] = [] for _g in range(group_count): groups.append([]) types = [1] @@ -599,9 +599,9 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): return groups - def _add_dist_entry_if_possible(self, groups: List[List[Tuple[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: + 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 @@ -624,7 +624,7 @@ 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 @@ -639,7 +639,7 @@ 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 @@ -654,7 +654,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): biggest_entry_group.remove(biggest_entry) def _delete_random_dist_entry(self, - groups: List[List[Tuple[int, int]]]) -> None: + groups: list[list[tuple[int, int]]]) -> None: entry_count = 0 for group in groups: for _ in group: @@ -1025,7 +1025,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): 'text': wttxt })) - def _bot_levels_for_wave(self) -> List[List[Type[SpazBot]]]: + def _bot_levels_for_wave(self) -> list[list[type[SpazBot]]]: level = self._wavenum bot_types = [ BomberBot, BrawlerBot, TriggerBot, ChargerBot, BomberBotPro, @@ -1068,10 +1068,10 @@ 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[Union[Spawn, Spacing, Delay, None]]) -> None: - entries: List[Union[Spawn, Spacing, Delay, None]] = [] + self, group: list[tuple[int, int]], + bot_levels: list[list[type[SpazBot]]], + all_entries: list[Union[Spawn, Spacing, Delay, None]]) -> None: + entries: list[Union[Spawn, Spacing, Delay, None]] = [] for entry in group: bot_level = bot_levels[entry[0] - 1] bot_type = bot_level[random.randrange(len(bot_level))] @@ -1106,7 +1106,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): distribution = self._get_distribution(target_points, min_dudes, max_dudes, group_count, max_level) - all_entries: List[Union[Spawn, Spacing, Delay, None]] = [] + all_entries: list[Union[Spawn, Spacing, Delay, None]] = [] for group in distribution: self._add_entries_for_distribution_group(group, bot_levels, all_entries) @@ -1125,7 +1125,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): def add_bot_at_point(self, point: Point, - spaz_type: Type[SpazBot], + spaz_type: type[SpazBot], spawn_time: float = 1.0) -> None: """Add a new bot at a specified named point.""" if self._game_over: @@ -1137,7 +1137,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): def add_bot_at_angle(self, angle: float, - spaz_type: Type[SpazBot], + 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: diff --git a/dist/ba_data/python/bastd/game/race.py b/dist/ba_data/python/bastd/game/race.py index 0a058ac..c8e6f3d 100644 --- a/dist/ba_data/python/bastd/game/race.py +++ b/dist/ba_data/python/bastd/game/race.py @@ -18,8 +18,7 @@ from bastd.actor.scoreboard import Scoreboard from bastd.gameutils import SharedObjects if TYPE_CHECKING: - from typing import (Any, Type, Tuple, List, Sequence, Optional, Dict, - Union) + from typing import Any, Sequence, Optional, Union from bastd.actor.onscreentimer import OnScreenTimer @@ -83,7 +82,7 @@ class RaceGame(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('Laps', min_value=1, default=3, increment=1), ba.IntChoiceSetting( @@ -129,11 +128,11 @@ class RaceGame(ba.TeamGameActivity[Player, Team]): return settings @classmethod - def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool: + def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool: return issubclass(sessiontype, ba.MultiTeamSession) @classmethod - def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]: + def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]: return ba.getmaps('race') def __init__(self, settings: dict): @@ -148,15 +147,15 @@ class RaceGame(ba.TeamGameActivity[Player, Team]): self._beep_1_sound = ba.getsound('raceBeep1') self._beep_2_sound = ba.getsound('raceBeep2') self.race_region_material: Optional[ba.Material] = None - self._regions: List[RaceRegion] = [] + self._regions: list[RaceRegion] = [] self._team_finish_pts: Optional[int] = None self._time_text: Optional[ba.Actor] = None self._timer: Optional[OnScreenTimer] = None - self._race_mines: Optional[List[RaceMine]] = None + self._race_mines: Optional[list[RaceMine]] = None self._race_mine_timer: Optional[ba.Timer] = None self._scoreboard_timer: Optional[ba.Timer] = None self._player_order_update_timer: Optional[ba.Timer] = None - self._start_lights: Optional[List[ba.Node]] = None + self._start_lights: Optional[list[ba.Node]] = None self._bomb_spawn_timer: Optional[ba.Timer] = None self._laps = int(settings['Laps']) self._entire_team_must_finish = bool( diff --git a/dist/ba_data/python/bastd/game/runaround.py b/dist/ba_data/python/bastd/game/runaround.py index 711ee46..e22d458 100644 --- a/dist/ba_data/python/bastd/game/runaround.py +++ b/dist/ba_data/python/bastd/game/runaround.py @@ -26,7 +26,7 @@ from bastd.actor.spazbot import ( BomberBotPro, BrawlerBotPro) if TYPE_CHECKING: - from typing import Type, Any, List, Dict, Tuple, Sequence, Optional, Union + from typing import Any, Sequence, Optional, Union class Preset(Enum): @@ -51,7 +51,7 @@ class Point(Enum): @dataclass class Spawn: """Defines a bot spawn event.""" - type: Type[SpazBot] + type: type[SpazBot] path: int = 0 point: Optional[Point] = None @@ -65,7 +65,7 @@ class Spacing: @dataclass class Wave: """Defines a wave of enemies.""" - entries: List[Union[Spawn, Spacing, None]] + entries: list[Union[Spawn, Spacing, None]] class Player(ba.Player['Team']): @@ -93,7 +93,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): default_music = ba.MusicType.MARCHING # How fast our various bot types walk. - _bot_speed_map: Dict[Type[SpazBot], float] = { + _bot_speed_map: dict[type[SpazBot], float] = { BomberBot: 0.48, BomberBotPro: 0.48, BomberBotProShielded: 0.48, @@ -152,9 +152,9 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): self._score_region: Optional[ba.Actor] = None self._dingsound = ba.getsound('dingSmall') self._dingsoundhigh = ba.getsound('dingSmallHigh') - self._exclude_powerups: Optional[List[str]] = None + self._exclude_powerups: Optional[list[str]] = None self._have_tnt: Optional[bool] = None - self._waves: Optional[List[Wave]] = None + self._waves: Optional[list[Wave]] = None self._bots = SpazBotSet() self._tntspawner: Optional[TNTSpawner] = None self._lives_bg: Optional[ba.NodeActor] = None @@ -561,7 +561,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): fail_message = None else: score = None - fail_message = 'Reach wave 2 to rank.' + fail_message = ba.Lstr(resource='reachWave2Text') self.end(delay=delay, results={ @@ -571,7 +571,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): 'playerinfos': self.initialplayerinfos }) - def _on_got_scores_to_beat(self, scores: List[Dict[str, Any]]) -> None: + def _on_got_scores_to_beat(self, scores: list[dict[str, Any]]) -> None: self._show_standard_scores_to_beat_ui(scores) def _update_waves(self) -> None: @@ -722,14 +722,14 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): t_sec = 0.0 base_delay = 0.5 delay = 0.0 - bot_types: List[Union[Spawn, Spacing, None]] = [] + bot_types: list[Union[Spawn, Spacing, None]] = [] if self._preset in {Preset.ENDLESS, Preset.ENDLESS_TOURNAMENT}: level = self._wavenum target_points = (level + 1) * 8.0 group_count = random.randint(1, 3) - entries: List[Union[Spawn, Spacing, None]] = [] - spaz_types: List[Tuple[Type[SpazBot], float]] = [] + entries: list[Union[Spawn, Spacing, None]] = [] + spaz_types: list[tuple[type[SpazBot], float]] = [] if level < 6: spaz_types += [(BomberBot, 5.0)] if level < 10: @@ -751,7 +751,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): ] * (1 + (level - 7) // 3) # Bot type, their effect on target points. - defender_types: List[Tuple[Type[SpazBot], float]] = [ + defender_types: list[tuple[type[SpazBot], float]] = [ (BomberBot, 0.9), (BrawlerBot, 0.9), (TriggerBot, 0.85), @@ -815,8 +815,8 @@ 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( @@ -979,7 +979,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): def add_bot_at_point(self, point: Point, - spaztype: Type[SpazBot], + spaztype: type[SpazBot], path: int, spawn_time: float = 0.1) -> None: """Add the given type bot with the given delay (in seconds).""" @@ -1152,7 +1152,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): return super().handlemessage(msg) return None - def _get_bot_speed(self, bot_type: Type[SpazBot]) -> float: + 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(): ' + diff --git a/dist/ba_data/python/bastd/game/targetpractice.py b/dist/ba_data/python/bastd/game/targetpractice.py index 91d0e13..c621306 100644 --- a/dist/ba_data/python/bastd/game/targetpractice.py +++ b/dist/ba_data/python/bastd/game/targetpractice.py @@ -17,7 +17,7 @@ from bastd.actor.bomb import Bomb from bastd.actor.popuptext import PopupText if TYPE_CHECKING: - from typing import Any, Type, List, Dict, Optional, Sequence + from typing import Any, Optional, Sequence from bastd.actor.bomb import Blast @@ -49,11 +49,11 @@ class TargetPracticeGame(ba.TeamGameActivity[Player, Team]): default_music = ba.MusicType.FORWARD_MARCH @classmethod - def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]: + def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]: return ['Doom Shroom'] @classmethod - def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool: + 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)) @@ -61,7 +61,7 @@ class TargetPracticeGame(ba.TeamGameActivity[Player, Team]): def __init__(self, settings: dict): super().__init__(settings) self._scoreboard = Scoreboard() - self._targets: List[Target] = [] + self._targets: list[Target] = [] self._update_timer: Optional[ba.Timer] = None self._countdown: Optional[OnScreenCountdown] = None self._target_count = int(settings['Target Count']) @@ -280,7 +280,7 @@ class Target(ba.Actor): # Inform our activity that we were hit self._hit = True activity.handlemessage(self.TargetHitMessage()) - keys: Dict[float, Sequence[float]] = { + keys: dict[float, Sequence[float]] = { 0.0: (1.0, 0.0, 0.0), 0.049: (1.0, 0.0, 0.0), 0.05: (1.0, 1.0, 1.0), diff --git a/dist/ba_data/python/bastd/game/thelaststand.py b/dist/ba_data/python/bastd/game/thelaststand.py index 5a24cc4..b423591 100644 --- a/dist/ba_data/python/bastd/game/thelaststand.py +++ b/dist/ba_data/python/bastd/game/thelaststand.py @@ -21,7 +21,7 @@ from bastd.actor.spazbot import (SpazBotSet, SpazBotDiedMessage, BomberBot, ChargerBot, StickyBot, ExplodeyBot) if TYPE_CHECKING: - from typing import Any, Dict, Type, List, Optional, Sequence + from typing import Any, Optional, Sequence from bastd.actor.spazbot import SpazBot @@ -70,7 +70,7 @@ class TheLastStandGame(ba.CoopGameActivity[Player, Team]): self._powerup_center = (0, 7, -4.14) self._powerup_spread = (7, 2) self._preset = str(settings.get('preset', 'default')) - self._excludepowerups: List[str] = [] + self._excludepowerups: list[str] = [] self._scoreboard: Optional[Scoreboard] = None self._score = 0 self._bots = SpazBotSet() @@ -185,11 +185,11 @@ class TheLastStandGame(ba.CoopGameActivity[Player, Team]): 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], + 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]] = [] + playerpts: list[Sequence[float]] = [] for player in self.players: try: if player.is_alive(): @@ -220,7 +220,7 @@ class TheLastStandGame(ba.CoopGameActivity[Player, Team]): # Now go back through and see where this value falls. total = 0 - bottype: Optional[Type[SpazBot]] = None + bottype: Optional[type[SpazBot]] = None for spawntype, spawninfo in self._bot_spawn_types.items(): total += spawninfo.spawnrate if randval <= total: @@ -284,7 +284,7 @@ class TheLastStandGame(ba.CoopGameActivity[Player, Team]): else: super().handlemessage(msg) - def _on_got_scores_to_beat(self, scores: List[Dict[str, Any]]) -> None: + def _on_got_scores_to_beat(self, scores: list[dict[str, Any]]) -> None: self._show_standard_scores_to_beat_ui(scores) def end_game(self) -> None: diff --git a/dist/ba_data/python/bastd/gameutils.py b/dist/ba_data/python/bastd/gameutils.py index 280e574..01a913f 100644 --- a/dist/ba_data/python/bastd/gameutils.py +++ b/dist/ba_data/python/bastd/gameutils.py @@ -9,7 +9,7 @@ from typing import TYPE_CHECKING import ba if TYPE_CHECKING: - from typing import Sequence, Optional + from typing import Optional class SharedObjects: diff --git a/dist/ba_data/python/bastd/keyboard/__pycache__/__init__.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/keyboard/__pycache__/__init__.cpython-39.opt-1.pyc new file mode 100644 index 0000000..de6c0cc Binary files /dev/null and b/dist/ba_data/python/bastd/keyboard/__pycache__/__init__.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/keyboard/__pycache__/englishkeyboard.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/keyboard/__pycache__/englishkeyboard.cpython-39.opt-1.pyc new file mode 100644 index 0000000..3398c40 Binary files /dev/null and b/dist/ba_data/python/bastd/keyboard/__pycache__/englishkeyboard.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/keyboard/englishkeyboard.py b/dist/ba_data/python/bastd/keyboard/englishkeyboard.py index a7e197d..8b8ff49 100644 --- a/dist/ba_data/python/bastd/keyboard/englishkeyboard.py +++ b/dist/ba_data/python/bastd/keyboard/englishkeyboard.py @@ -12,13 +12,13 @@ from typing import TYPE_CHECKING import ba if TYPE_CHECKING: - from typing import Iterable, List, Tuple, Dict + from typing import Iterable -def split(chars: Iterable[str], maxlen: int) -> List[List[str]]: +def split(chars: Iterable[str], maxlen: int) -> list[list[str]]: """Returns char groups with a fixed number of elements""" result = [] - shatter: List[str] = [] + shatter: list[str] = [] for i in chars: if len(shatter) < maxlen: shatter.append(i) @@ -32,7 +32,7 @@ def split(chars: Iterable[str], maxlen: int) -> List[List[str]]: return result -def generate_emojis(maxlen: int) -> List[List[str]]: +def generate_emojis(maxlen: int) -> list[list[str]]: """Generates a lot of UTF8 emojis prepared for ba.Keyboard pages""" all_emojis = split([chr(i) for i in range(0x1F601, 0x1F650)], maxlen) all_emojis += split([chr(i) for i in range(0x2702, 0x27B1)], maxlen) @@ -49,7 +49,7 @@ class EnglishKeyboard(ba.Keyboard): ('z', 'x', 'c', 'v', 'b', 'n', 'm')] nums = ('1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '/', ':', ';', '(', ')', '$', '&', '@', '"', '.', ',', '?', '!', '\'', '_') - pages: Dict[str, Tuple[str, ...]] = { + 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 5889631..e7f2db6 100644 --- a/dist/ba_data/python/bastd/mainmenu.py +++ b/dist/ba_data/python/bastd/mainmenu.py @@ -13,7 +13,7 @@ import ba import _ba if TYPE_CHECKING: - from typing import Any, List, Optional + from typing import Any, Optional # FIXME: Clean this up if I ever revisit it. # pylint: disable=attribute-defined-outside-init @@ -34,7 +34,7 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]): random.seed(123) self._logo_node: Optional[ba.Node] = None self._custom_logo_tex_name: Optional[str] = None - self._word_actors: List[ba.Actor] = [] + self._word_actors: list[ba.Actor] = [] app = ba.app # FIXME: We shouldn't be doing things conditionally based on whether @@ -364,7 +364,7 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]): return with ba.Context(activity): - self._phrases: List[str] = [] + self._phrases: list[str] = [] # Show upcoming achievements in non-vr versions # (currently too hard to read in vr). diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/__init__.cpython-38.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/__init__.cpython-38.pyc index d2d57e1..68827f2 100644 Binary files a/dist/ba_data/python/bastd/mapdata/__pycache__/__init__.cpython-38.pyc and b/dist/ba_data/python/bastd/mapdata/__pycache__/__init__.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/__init__.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/__init__.cpython-39.opt-1.pyc new file mode 100644 index 0000000..de6c0cc Binary files /dev/null and b/dist/ba_data/python/bastd/mapdata/__pycache__/__init__.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/__init__.cpython-39.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..f7168ca Binary files /dev/null and b/dist/ba_data/python/bastd/mapdata/__pycache__/__init__.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/big_g.cpython-38.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/big_g.cpython-38.pyc index 2a086a7..132f13e 100644 Binary files a/dist/ba_data/python/bastd/mapdata/__pycache__/big_g.cpython-38.pyc and b/dist/ba_data/python/bastd/mapdata/__pycache__/big_g.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/big_g.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/big_g.cpython-39.opt-1.pyc new file mode 100644 index 0000000..da0a0de Binary files /dev/null and b/dist/ba_data/python/bastd/mapdata/__pycache__/big_g.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/big_g.cpython-39.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/big_g.cpython-39.pyc new file mode 100644 index 0000000..a58f72c Binary files /dev/null and b/dist/ba_data/python/bastd/mapdata/__pycache__/big_g.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/bridgit.cpython-38.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/bridgit.cpython-38.pyc index 2347411..e85bf45 100644 Binary files a/dist/ba_data/python/bastd/mapdata/__pycache__/bridgit.cpython-38.pyc and b/dist/ba_data/python/bastd/mapdata/__pycache__/bridgit.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/bridgit.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/bridgit.cpython-39.opt-1.pyc new file mode 100644 index 0000000..f54611f Binary files /dev/null and b/dist/ba_data/python/bastd/mapdata/__pycache__/bridgit.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/bridgit.cpython-39.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/bridgit.cpython-39.pyc new file mode 100644 index 0000000..5b6c171 Binary files /dev/null and b/dist/ba_data/python/bastd/mapdata/__pycache__/bridgit.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/courtyard.cpython-38.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/courtyard.cpython-38.pyc index c8e8ab6..c7d18a5 100644 Binary files a/dist/ba_data/python/bastd/mapdata/__pycache__/courtyard.cpython-38.pyc and b/dist/ba_data/python/bastd/mapdata/__pycache__/courtyard.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/courtyard.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/courtyard.cpython-39.opt-1.pyc new file mode 100644 index 0000000..85a4455 Binary files /dev/null and b/dist/ba_data/python/bastd/mapdata/__pycache__/courtyard.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/courtyard.cpython-39.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/courtyard.cpython-39.pyc new file mode 100644 index 0000000..2604eb6 Binary files /dev/null and b/dist/ba_data/python/bastd/mapdata/__pycache__/courtyard.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/crag_castle.cpython-38.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/crag_castle.cpython-38.pyc index ba2fea0..77cc01b 100644 Binary files a/dist/ba_data/python/bastd/mapdata/__pycache__/crag_castle.cpython-38.pyc and b/dist/ba_data/python/bastd/mapdata/__pycache__/crag_castle.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/crag_castle.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/crag_castle.cpython-39.opt-1.pyc new file mode 100644 index 0000000..0c54b78 Binary files /dev/null and b/dist/ba_data/python/bastd/mapdata/__pycache__/crag_castle.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/crag_castle.cpython-39.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/crag_castle.cpython-39.pyc new file mode 100644 index 0000000..87d876a Binary files /dev/null and b/dist/ba_data/python/bastd/mapdata/__pycache__/crag_castle.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/doom_shroom.cpython-38.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/doom_shroom.cpython-38.pyc index b37563e..bb648ee 100644 Binary files a/dist/ba_data/python/bastd/mapdata/__pycache__/doom_shroom.cpython-38.pyc and b/dist/ba_data/python/bastd/mapdata/__pycache__/doom_shroom.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/doom_shroom.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/doom_shroom.cpython-39.opt-1.pyc new file mode 100644 index 0000000..47a70e6 Binary files /dev/null and b/dist/ba_data/python/bastd/mapdata/__pycache__/doom_shroom.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/doom_shroom.cpython-39.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/doom_shroom.cpython-39.pyc new file mode 100644 index 0000000..e993e84 Binary files /dev/null and b/dist/ba_data/python/bastd/mapdata/__pycache__/doom_shroom.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/football_stadium.cpython-38.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/football_stadium.cpython-38.pyc index c043763..dbbc773 100644 Binary files a/dist/ba_data/python/bastd/mapdata/__pycache__/football_stadium.cpython-38.pyc and b/dist/ba_data/python/bastd/mapdata/__pycache__/football_stadium.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/football_stadium.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/football_stadium.cpython-39.opt-1.pyc new file mode 100644 index 0000000..1175489 Binary files /dev/null and b/dist/ba_data/python/bastd/mapdata/__pycache__/football_stadium.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/football_stadium.cpython-39.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/football_stadium.cpython-39.pyc new file mode 100644 index 0000000..69682c5 Binary files /dev/null and b/dist/ba_data/python/bastd/mapdata/__pycache__/football_stadium.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/happy_thoughts.cpython-38.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/happy_thoughts.cpython-38.pyc index 4e290f5..37b5490 100644 Binary files a/dist/ba_data/python/bastd/mapdata/__pycache__/happy_thoughts.cpython-38.pyc and b/dist/ba_data/python/bastd/mapdata/__pycache__/happy_thoughts.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/happy_thoughts.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/happy_thoughts.cpython-39.opt-1.pyc new file mode 100644 index 0000000..a51d2fc Binary files /dev/null and b/dist/ba_data/python/bastd/mapdata/__pycache__/happy_thoughts.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/happy_thoughts.cpython-39.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/happy_thoughts.cpython-39.pyc new file mode 100644 index 0000000..59cce52 Binary files /dev/null and b/dist/ba_data/python/bastd/mapdata/__pycache__/happy_thoughts.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/hockey_stadium.cpython-38.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/hockey_stadium.cpython-38.pyc index c1f8632..9593902 100644 Binary files a/dist/ba_data/python/bastd/mapdata/__pycache__/hockey_stadium.cpython-38.pyc and b/dist/ba_data/python/bastd/mapdata/__pycache__/hockey_stadium.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/hockey_stadium.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/hockey_stadium.cpython-39.opt-1.pyc new file mode 100644 index 0000000..75b2437 Binary files /dev/null and b/dist/ba_data/python/bastd/mapdata/__pycache__/hockey_stadium.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/hockey_stadium.cpython-39.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/hockey_stadium.cpython-39.pyc new file mode 100644 index 0000000..227f3df Binary files /dev/null and b/dist/ba_data/python/bastd/mapdata/__pycache__/hockey_stadium.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/lake_frigid.cpython-38.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/lake_frigid.cpython-38.pyc index 10acaec..6e28b74 100644 Binary files a/dist/ba_data/python/bastd/mapdata/__pycache__/lake_frigid.cpython-38.pyc and b/dist/ba_data/python/bastd/mapdata/__pycache__/lake_frigid.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/lake_frigid.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/lake_frigid.cpython-39.opt-1.pyc new file mode 100644 index 0000000..7f013f9 Binary files /dev/null and b/dist/ba_data/python/bastd/mapdata/__pycache__/lake_frigid.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/lake_frigid.cpython-39.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/lake_frigid.cpython-39.pyc new file mode 100644 index 0000000..66d7c72 Binary files /dev/null and b/dist/ba_data/python/bastd/mapdata/__pycache__/lake_frigid.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/monkey_face.cpython-38.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/monkey_face.cpython-38.pyc index 5460bef..7410971 100644 Binary files a/dist/ba_data/python/bastd/mapdata/__pycache__/monkey_face.cpython-38.pyc and b/dist/ba_data/python/bastd/mapdata/__pycache__/monkey_face.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/monkey_face.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/monkey_face.cpython-39.opt-1.pyc new file mode 100644 index 0000000..f8dac17 Binary files /dev/null and b/dist/ba_data/python/bastd/mapdata/__pycache__/monkey_face.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/monkey_face.cpython-39.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/monkey_face.cpython-39.pyc new file mode 100644 index 0000000..a596b08 Binary files /dev/null and b/dist/ba_data/python/bastd/mapdata/__pycache__/monkey_face.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/rampage.cpython-38.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/rampage.cpython-38.pyc index 2bc4535..c234e91 100644 Binary files a/dist/ba_data/python/bastd/mapdata/__pycache__/rampage.cpython-38.pyc and b/dist/ba_data/python/bastd/mapdata/__pycache__/rampage.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/rampage.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/rampage.cpython-39.opt-1.pyc new file mode 100644 index 0000000..d4f1305 Binary files /dev/null and b/dist/ba_data/python/bastd/mapdata/__pycache__/rampage.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/rampage.cpython-39.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/rampage.cpython-39.pyc new file mode 100644 index 0000000..e89723f Binary files /dev/null and b/dist/ba_data/python/bastd/mapdata/__pycache__/rampage.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/roundabout.cpython-38.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/roundabout.cpython-38.pyc index ccc2c30..1ad98af 100644 Binary files a/dist/ba_data/python/bastd/mapdata/__pycache__/roundabout.cpython-38.pyc and b/dist/ba_data/python/bastd/mapdata/__pycache__/roundabout.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/roundabout.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/roundabout.cpython-39.opt-1.pyc new file mode 100644 index 0000000..545882b Binary files /dev/null and b/dist/ba_data/python/bastd/mapdata/__pycache__/roundabout.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/roundabout.cpython-39.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/roundabout.cpython-39.pyc new file mode 100644 index 0000000..a3c5a08 Binary files /dev/null and b/dist/ba_data/python/bastd/mapdata/__pycache__/roundabout.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/step_right_up.cpython-38.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/step_right_up.cpython-38.pyc index 9feadea..8dce2f6 100644 Binary files a/dist/ba_data/python/bastd/mapdata/__pycache__/step_right_up.cpython-38.pyc and b/dist/ba_data/python/bastd/mapdata/__pycache__/step_right_up.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/step_right_up.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/step_right_up.cpython-39.opt-1.pyc new file mode 100644 index 0000000..16a1152 Binary files /dev/null and b/dist/ba_data/python/bastd/mapdata/__pycache__/step_right_up.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/step_right_up.cpython-39.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/step_right_up.cpython-39.pyc new file mode 100644 index 0000000..7229ebd Binary files /dev/null and b/dist/ba_data/python/bastd/mapdata/__pycache__/step_right_up.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/the_pad.cpython-38.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/the_pad.cpython-38.pyc index 82b174d..fce3af1 100644 Binary files a/dist/ba_data/python/bastd/mapdata/__pycache__/the_pad.cpython-38.pyc and b/dist/ba_data/python/bastd/mapdata/__pycache__/the_pad.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/the_pad.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/the_pad.cpython-39.opt-1.pyc new file mode 100644 index 0000000..9b44507 Binary files /dev/null and b/dist/ba_data/python/bastd/mapdata/__pycache__/the_pad.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/the_pad.cpython-39.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/the_pad.cpython-39.pyc new file mode 100644 index 0000000..0590925 Binary files /dev/null and b/dist/ba_data/python/bastd/mapdata/__pycache__/the_pad.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/tip_top.cpython-38.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/tip_top.cpython-38.pyc index ae6125f..16e2c7b 100644 Binary files a/dist/ba_data/python/bastd/mapdata/__pycache__/tip_top.cpython-38.pyc and b/dist/ba_data/python/bastd/mapdata/__pycache__/tip_top.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/tip_top.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/tip_top.cpython-39.opt-1.pyc new file mode 100644 index 0000000..63208fe Binary files /dev/null and b/dist/ba_data/python/bastd/mapdata/__pycache__/tip_top.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/tip_top.cpython-39.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/tip_top.cpython-39.pyc new file mode 100644 index 0000000..a8e216f Binary files /dev/null and b/dist/ba_data/python/bastd/mapdata/__pycache__/tip_top.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/tower_d.cpython-38.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/tower_d.cpython-38.pyc index 29a3ea1..a885cc4 100644 Binary files a/dist/ba_data/python/bastd/mapdata/__pycache__/tower_d.cpython-38.pyc and b/dist/ba_data/python/bastd/mapdata/__pycache__/tower_d.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/tower_d.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/tower_d.cpython-39.opt-1.pyc new file mode 100644 index 0000000..79725b7 Binary files /dev/null and b/dist/ba_data/python/bastd/mapdata/__pycache__/tower_d.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/tower_d.cpython-39.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/tower_d.cpython-39.pyc new file mode 100644 index 0000000..9004573 Binary files /dev/null and b/dist/ba_data/python/bastd/mapdata/__pycache__/tower_d.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/zig_zag.cpython-38.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/zig_zag.cpython-38.pyc index b5be610..4471319 100644 Binary files a/dist/ba_data/python/bastd/mapdata/__pycache__/zig_zag.cpython-38.pyc and b/dist/ba_data/python/bastd/mapdata/__pycache__/zig_zag.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/zig_zag.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/zig_zag.cpython-39.opt-1.pyc new file mode 100644 index 0000000..26ceecf Binary files /dev/null and b/dist/ba_data/python/bastd/mapdata/__pycache__/zig_zag.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/mapdata/__pycache__/zig_zag.cpython-39.pyc b/dist/ba_data/python/bastd/mapdata/__pycache__/zig_zag.cpython-39.pyc new file mode 100644 index 0000000..dcc006c Binary files /dev/null and b/dist/ba_data/python/bastd/mapdata/__pycache__/zig_zag.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/maps.py b/dist/ba_data/python/bastd/maps.py index 03f4125..7d3779e 100644 --- a/dist/ba_data/python/bastd/maps.py +++ b/dist/ba_data/python/bastd/maps.py @@ -11,7 +11,7 @@ import ba from bastd.gameutils import SharedObjects if TYPE_CHECKING: - from typing import Any, List, Dict + from typing import Any class HockeyStadium(ba.Map): @@ -21,7 +21,7 @@ class HockeyStadium(ba.Map): name = 'Hockey Stadium' @classmethod - def get_play_types(cls) -> List[str]: + def get_play_types(cls) -> list[str]: """Return valid play types for this map.""" return ['melee', 'hockey', 'team_flag', 'keep_away'] @@ -31,7 +31,7 @@ class HockeyStadium(ba.Map): @classmethod def on_preload(cls) -> Any: - data: Dict[str, Any] = { + data: dict[str, Any] = { 'models': (ba.getmodel('hockeyStadiumOuter'), ba.getmodel('hockeyStadiumInner'), ba.getmodel('hockeyStadiumStands')), @@ -106,7 +106,7 @@ class FootballStadium(ba.Map): name = 'Football Stadium' @classmethod - def get_play_types(cls) -> List[str]: + def get_play_types(cls) -> list[str]: """Return valid play types for this map.""" return ['melee', 'football', 'team_flag', 'keep_away'] @@ -116,7 +116,7 @@ class FootballStadium(ba.Map): @classmethod def on_preload(cls) -> Any: - data: Dict[str, Any] = { + data: dict[str, Any] = { 'model': ba.getmodel('footballStadium'), 'vr_fill_model': ba.getmodel('footballStadiumVRFill'), 'collide_model': ba.getcollidemodel('footballStadiumCollide'), @@ -170,7 +170,7 @@ class Bridgit(ba.Map): dataname = 'bridgit' @classmethod - def get_play_types(cls) -> List[str]: + def get_play_types(cls) -> list[str]: """Return valid play types for this map.""" # print('getting playtypes', cls._getdata()['play_types']) return ['melee', 'team_flag', 'keep_away'] @@ -181,7 +181,7 @@ class Bridgit(ba.Map): @classmethod def on_preload(cls) -> Any: - data: Dict[str, Any] = { + data: dict[str, Any] = { 'model_top': ba.getmodel('bridgitLevelTop'), 'model_bottom': ba.getmodel('bridgitLevelBottom'), 'model_bg': ba.getmodel('natureBackground'), @@ -264,7 +264,7 @@ class BigG(ba.Map): name = 'Big G' @classmethod - def get_play_types(cls) -> List[str]: + 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', @@ -277,7 +277,7 @@ class BigG(ba.Map): @classmethod def on_preload(cls) -> Any: - data: Dict[str, Any] = { + data: dict[str, Any] = { 'model_top': ba.getmodel('bigG'), 'model_bottom': ba.getmodel('bigGBottom'), 'model_bg': ba.getmodel('natureBackground'), @@ -361,7 +361,7 @@ class Roundabout(ba.Map): name = 'Roundabout' @classmethod - def get_play_types(cls) -> List[str]: + def get_play_types(cls) -> list[str]: """Return valid play types for this map.""" return ['melee', 'keep_away', 'team_flag'] @@ -371,7 +371,7 @@ class Roundabout(ba.Map): @classmethod def on_preload(cls) -> Any: - data: Dict[str, Any] = { + data: dict[str, Any] = { 'model': ba.getmodel('roundaboutLevel'), 'model_bottom': ba.getmodel('roundaboutLevelBottom'), 'model_bg': ba.getmodel('natureBackground'), @@ -455,7 +455,7 @@ class MonkeyFace(ba.Map): name = 'Monkey Face' @classmethod - def get_play_types(cls) -> List[str]: + def get_play_types(cls) -> list[str]: """Return valid play types for this map.""" return ['melee', 'keep_away', 'team_flag'] @@ -465,7 +465,7 @@ class MonkeyFace(ba.Map): @classmethod def on_preload(cls) -> Any: - data: Dict[str, Any] = { + data: dict[str, Any] = { 'model': ba.getmodel('monkeyFaceLevel'), 'bottom_model': ba.getmodel('monkeyFaceLevelBottom'), 'model_bg': ba.getmodel('natureBackground'), @@ -549,7 +549,7 @@ class ZigZag(ba.Map): name = 'Zigzag' @classmethod - def get_play_types(cls) -> List[str]: + 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' @@ -561,7 +561,7 @@ class ZigZag(ba.Map): @classmethod def on_preload(cls) -> Any: - data: Dict[str, Any] = { + data: dict[str, Any] = { 'model': ba.getmodel('zigZagLevel'), 'model_bottom': ba.getmodel('zigZagLevelBottom'), 'model_bg': ba.getmodel('natureBackground'), @@ -643,7 +643,7 @@ class ThePad(ba.Map): name = 'The Pad' @classmethod - def get_play_types(cls) -> List[str]: + def get_play_types(cls) -> list[str]: """Return valid play types for this map.""" return ['melee', 'keep_away', 'team_flag', 'king_of_the_hill'] @@ -653,7 +653,7 @@ class ThePad(ba.Map): @classmethod def on_preload(cls) -> Any: - data: Dict[str, Any] = { + data: dict[str, Any] = { 'model': ba.getmodel('thePadLevel'), 'bottom_model': ba.getmodel('thePadLevelBottom'), 'collide_model': ba.getcollidemodel('thePadLevelCollide'), @@ -724,7 +724,7 @@ class DoomShroom(ba.Map): name = 'Doom Shroom' @classmethod - def get_play_types(cls) -> List[str]: + def get_play_types(cls) -> list[str]: """Return valid play types for this map.""" return ['melee', 'keep_away', 'team_flag'] @@ -734,7 +734,7 @@ class DoomShroom(ba.Map): @classmethod def on_preload(cls) -> Any: - data: Dict[str, Any] = { + data: dict[str, Any] = { 'model': ba.getmodel('doomShroomLevel'), 'collide_model': ba.getcollidemodel('doomShroomLevelCollide'), 'tex': ba.gettexture('doomShroomLevelColor'), @@ -814,7 +814,7 @@ class LakeFrigid(ba.Map): name = 'Lake Frigid' @classmethod - def get_play_types(cls) -> List[str]: + def get_play_types(cls) -> list[str]: """Return valid play types for this map.""" return ['melee', 'keep_away', 'team_flag', 'race'] @@ -824,7 +824,7 @@ class LakeFrigid(ba.Map): @classmethod def on_preload(cls) -> Any: - data: Dict[str, Any] = { + data: dict[str, Any] = { 'model': ba.getmodel('lakeFrigid'), 'model_top': ba.getmodel('lakeFrigidTop'), 'model_reflections': ba.getmodel('lakeFrigidReflections'), @@ -895,7 +895,7 @@ class TipTop(ba.Map): name = 'Tip Top' @classmethod - def get_play_types(cls) -> List[str]: + def get_play_types(cls) -> list[str]: """Return valid play types for this map.""" return ['melee', 'keep_away', 'team_flag', 'king_of_the_hill'] @@ -905,7 +905,7 @@ class TipTop(ba.Map): @classmethod def on_preload(cls) -> Any: - data: Dict[str, Any] = { + data: dict[str, Any] = { 'model': ba.getmodel('tipTopLevel'), 'bottom_model': ba.getmodel('tipTopLevelBottom'), 'collide_model': ba.getcollidemodel('tipTopLevelCollide'), @@ -967,7 +967,7 @@ class CragCastle(ba.Map): name = 'Crag Castle' @classmethod - def get_play_types(cls) -> List[str]: + def get_play_types(cls) -> list[str]: """Return valid play types for this map.""" return ['melee', 'keep_away', 'team_flag', 'conquest'] @@ -977,7 +977,7 @@ class CragCastle(ba.Map): @classmethod def on_preload(cls) -> Any: - data: Dict[str, Any] = { + data: dict[str, Any] = { 'model': ba.getmodel('cragCastleLevel'), 'bottom_model': ba.getmodel('cragCastleLevelBottom'), 'collide_model': ba.getcollidemodel('cragCastleLevelCollide'), @@ -1052,7 +1052,7 @@ class TowerD(ba.Map): name = 'Tower D' @classmethod - def get_play_types(cls) -> List[str]: + def get_play_types(cls) -> list[str]: """Return valid play types for this map.""" return [] @@ -1062,7 +1062,7 @@ class TowerD(ba.Map): @classmethod def on_preload(cls) -> Any: - data: Dict[str, Any] = { + data: dict[str, Any] = { 'model': ba.getmodel('towerDLevel'), 'model_bottom': @@ -1169,7 +1169,7 @@ class HappyThoughts(ba.Map): name = 'Happy Thoughts' @classmethod - def get_play_types(cls) -> List[str]: + 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' @@ -1181,7 +1181,7 @@ class HappyThoughts(ba.Map): @classmethod def on_preload(cls) -> Any: - data: Dict[str, Any] = { + data: dict[str, Any] = { 'model': ba.getmodel('alwaysLandLevel'), 'bottom_model': ba.getmodel('alwaysLandLevelBottom'), 'bgmodel': ba.getmodel('alwaysLandBG'), @@ -1275,7 +1275,7 @@ class StepRightUp(ba.Map): name = 'Step Right Up' @classmethod - def get_play_types(cls) -> List[str]: + def get_play_types(cls) -> list[str]: """Return valid play types for this map.""" return ['melee', 'keep_away', 'team_flag', 'conquest'] @@ -1285,7 +1285,7 @@ class StepRightUp(ba.Map): @classmethod def on_preload(cls) -> Any: - data: Dict[str, Any] = { + data: dict[str, Any] = { 'model': ba.getmodel('stepRightUpLevel'), 'model_bottom': ba.getmodel('stepRightUpLevelBottom'), 'collide_model': ba.getcollidemodel('stepRightUpLevelCollide'), @@ -1350,7 +1350,7 @@ class Courtyard(ba.Map): name = 'Courtyard' @classmethod - def get_play_types(cls) -> List[str]: + def get_play_types(cls) -> list[str]: """Return valid play types for this map.""" return ['melee', 'keep_away', 'team_flag'] @@ -1360,7 +1360,7 @@ class Courtyard(ba.Map): @classmethod def on_preload(cls) -> Any: - data: Dict[str, Any] = { + data: dict[str, Any] = { 'model': ba.getmodel('courtyardLevel'), 'model_bottom': ba.getmodel('courtyardLevelBottom'), 'collide_model': ba.getcollidemodel('courtyardLevelCollide'), @@ -1456,7 +1456,7 @@ class Rampage(ba.Map): name = 'Rampage' @classmethod - def get_play_types(cls) -> List[str]: + def get_play_types(cls) -> list[str]: """Return valid play types for this map.""" return ['melee', 'keep_away', 'team_flag'] @@ -1466,7 +1466,7 @@ class Rampage(ba.Map): @classmethod def on_preload(cls) -> Any: - data: Dict[str, Any] = { + data: dict[str, Any] = { 'model': ba.getmodel('rampageLevel'), 'bottom_model': ba.getmodel('rampageLevelBottom'), 'collide_model': ba.getcollidemodel('rampageLevelCollide'), diff --git a/dist/ba_data/python/bastd/session/__pycache__/__init__.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/session/__pycache__/__init__.cpython-39.opt-1.pyc new file mode 100644 index 0000000..de6c0cc Binary files /dev/null and b/dist/ba_data/python/bastd/session/__pycache__/__init__.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/stdmap.py b/dist/ba_data/python/bastd/stdmap.py index 237ea4a..4522eac 100644 --- a/dist/ba_data/python/bastd/stdmap.py +++ b/dist/ba_data/python/bastd/stdmap.py @@ -9,13 +9,14 @@ from typing import TYPE_CHECKING import ba if TYPE_CHECKING: - from typing import Dict, Any, Optional + from typing import Any, Optional -def _get_map_data(name: str) -> Dict[str, Any]: +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') 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 @@ -25,10 +26,10 @@ class StdMap(ba.Map): """A map completely defined by asset data. """ - _data: Optional[Dict[str, Any]] = None + _data: Optional[dict[str, Any]] = None @classmethod - def _getdata(cls) -> Dict[str, Any]: + def _getdata(cls) -> dict[str, Any]: if cls._data is None: cls._data = _get_map_data('bridgit') return cls._data diff --git a/dist/ba_data/python/bastd/tutorial.py b/dist/ba_data/python/bastd/tutorial.py index 39bad74..6eb7816 100644 --- a/dist/ba_data/python/bastd/tutorial.py +++ b/dist/ba_data/python/bastd/tutorial.py @@ -23,8 +23,7 @@ import ba from bastd.actor import spaz as basespaz if TYPE_CHECKING: - from typing import (Any, Optional, Dict, List, Tuple, Callable, Sequence, - Union) + from typing import Any, Optional, Callable, Sequence, Union def _safesetattr(node: Optional[ba.Node], attr: str, value: Any) -> None: @@ -187,7 +186,7 @@ class TutorialActivity(ba.Activity[Player, Team]): self.current_spaz: Optional[basespaz.Spaz] = None self._benchmark_type = getattr(ba.getsession(), 'benchmark_type', None) self.last_start_time: Optional[int] = None - self.cycle_times: List[int] = [] + self.cycle_times: list[int] = [] self.allow_pausing = True self.allow_kick_idle_players = False self._issued_warning = False @@ -206,8 +205,8 @@ class TutorialActivity(ba.Activity[Player, Team]): self._skip_text: Optional[ba.Node] = None self._skip_count_text: Optional[ba.Node] = None self._scale: Optional[float] = None - self._stick_base_position: Tuple[float, float] = (0.0, 0.0) - self._stick_nub_position: Tuple[float, float] = (0.0, 0.0) + self._stick_base_position: tuple[float, float] = (0.0, 0.0) + self._stick_nub_position: tuple[float, float] = (0.0, 0.0) self._stick_base_image_color: Sequence[float] = (1.0, 1.0, 1.0, 1.0) self._stick_nub_image_color: Sequence[float] = (1.0, 1.0, 1.0, 1.0) self._time: int = -1 @@ -220,10 +219,10 @@ class TutorialActivity(ba.Activity[Player, Team]): self._stick_nub_image: Optional[ba.Node] = None self.bomb_image_color = (1.0, 1.0, 1.0) self.pickup_image_color = (1.0, 1.0, 1.0) - self.control_ui_nodes: List[ba.Node] = [] - self.spazzes: Dict[int, basespaz.Spaz] = {} + self.control_ui_nodes: list[ba.Node] = [] + self.spazzes: dict[int, basespaz.Spaz] = {} self.jump_image_color = (1.0, 1.0, 1.0) - self._entries: List[Any] = [] + self._entries: list[Any] = [] self._read_entries_timer: Optional[ba.Timer] = None self._entry_timer: Optional[ba.Timer] = None @@ -300,7 +299,7 @@ class TutorialActivity(ba.Activity[Player, Team]): nub_size = 110.0 * scale p = (position[0] + center_offs, position[1] - offs) - def _sc(r: float, g: float, b: float) -> Tuple[float, float, float]: + def _sc(r: float, g: float, b: float) -> tuple[float, float, float]: return 0.6 * r, 0.6 * g, 0.6 * b self.jump_image_color = c = _sc(0.4, 1, 0.4) diff --git a/dist/ba_data/python/bastd/ui/__pycache__/__init__.cpython-38.pyc b/dist/ba_data/python/bastd/ui/__pycache__/__init__.cpython-38.pyc index ff358f6..0800ca5 100644 Binary files a/dist/ba_data/python/bastd/ui/__pycache__/__init__.cpython-38.pyc and b/dist/ba_data/python/bastd/ui/__pycache__/__init__.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/__init__.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/__init__.cpython-39.opt-1.pyc new file mode 100644 index 0000000..0cdbcf4 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/__init__.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/__init__.cpython-39.pyc b/dist/ba_data/python/bastd/ui/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..aa2361c Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/__init__.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/achievements.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/achievements.cpython-39.opt-1.pyc new file mode 100644 index 0000000..79ef3ba Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/achievements.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/appinvite.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/appinvite.cpython-38.opt-1.pyc index 0914167..a27692a 100644 Binary files a/dist/ba_data/python/bastd/ui/__pycache__/appinvite.cpython-38.opt-1.pyc and b/dist/ba_data/python/bastd/ui/__pycache__/appinvite.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/appinvite.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/appinvite.cpython-39.opt-1.pyc new file mode 100644 index 0000000..3a718db Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/appinvite.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/characterpicker.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/characterpicker.cpython-39.opt-1.pyc new file mode 100644 index 0000000..184e532 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/characterpicker.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/colorpicker.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/colorpicker.cpython-38.opt-1.pyc index e1c6cca..795965f 100644 Binary files a/dist/ba_data/python/bastd/ui/__pycache__/colorpicker.cpython-38.opt-1.pyc and b/dist/ba_data/python/bastd/ui/__pycache__/colorpicker.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/colorpicker.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/colorpicker.cpython-39.opt-1.pyc new file mode 100644 index 0000000..b769d1d Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/colorpicker.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/config.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/config.cpython-39.opt-1.pyc new file mode 100644 index 0000000..cd44ae3 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/config.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/configerror.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/configerror.cpython-39.opt-1.pyc new file mode 100644 index 0000000..8fbfa16 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/configerror.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/confirm.cpython-38.pyc b/dist/ba_data/python/bastd/ui/__pycache__/confirm.cpython-38.pyc index fdd4918..41dc118 100644 Binary files a/dist/ba_data/python/bastd/ui/__pycache__/confirm.cpython-38.pyc and b/dist/ba_data/python/bastd/ui/__pycache__/confirm.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/confirm.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/confirm.cpython-39.opt-1.pyc new file mode 100644 index 0000000..ef13cb2 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/confirm.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/confirm.cpython-39.pyc b/dist/ba_data/python/bastd/ui/__pycache__/confirm.cpython-39.pyc new file mode 100644 index 0000000..2398169 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/confirm.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/continues.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/continues.cpython-39.opt-1.pyc new file mode 100644 index 0000000..6983c8e Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/continues.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/creditslist.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/creditslist.cpython-38.opt-1.pyc index 23eb471..bc50ed2 100644 Binary files a/dist/ba_data/python/bastd/ui/__pycache__/creditslist.cpython-38.opt-1.pyc and b/dist/ba_data/python/bastd/ui/__pycache__/creditslist.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/creditslist.cpython-38.pyc b/dist/ba_data/python/bastd/ui/__pycache__/creditslist.cpython-38.pyc index 7206338..5d310e7 100644 Binary files a/dist/ba_data/python/bastd/ui/__pycache__/creditslist.cpython-38.pyc and b/dist/ba_data/python/bastd/ui/__pycache__/creditslist.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/creditslist.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/creditslist.cpython-39.opt-1.pyc new file mode 100644 index 0000000..ac28ac1 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/creditslist.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/creditslist.cpython-39.pyc b/dist/ba_data/python/bastd/ui/__pycache__/creditslist.cpython-39.pyc new file mode 100644 index 0000000..bd0fa18 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/creditslist.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/debug.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/debug.cpython-39.opt-1.pyc new file mode 100644 index 0000000..de165bf Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/debug.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/feedback.cpython-38.pyc b/dist/ba_data/python/bastd/ui/__pycache__/feedback.cpython-38.pyc index 860c000..f1acffd 100644 Binary files a/dist/ba_data/python/bastd/ui/__pycache__/feedback.cpython-38.pyc and b/dist/ba_data/python/bastd/ui/__pycache__/feedback.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/feedback.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/feedback.cpython-39.opt-1.pyc new file mode 100644 index 0000000..f9f8f3f Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/feedback.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/feedback.cpython-39.pyc b/dist/ba_data/python/bastd/ui/__pycache__/feedback.cpython-39.pyc new file mode 100644 index 0000000..d5f44ac Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/feedback.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/fileselector.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/fileselector.cpython-39.opt-1.pyc new file mode 100644 index 0000000..f4045a5 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/fileselector.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/getcurrency.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/getcurrency.cpython-38.opt-1.pyc index b47246e..fc26b34 100644 Binary files a/dist/ba_data/python/bastd/ui/__pycache__/getcurrency.cpython-38.opt-1.pyc and b/dist/ba_data/python/bastd/ui/__pycache__/getcurrency.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/getcurrency.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/getcurrency.cpython-39.opt-1.pyc new file mode 100644 index 0000000..84a8b03 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/getcurrency.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/getremote.cpython-38.pyc b/dist/ba_data/python/bastd/ui/__pycache__/getremote.cpython-38.pyc index ba7ed65..59f807c 100644 Binary files a/dist/ba_data/python/bastd/ui/__pycache__/getremote.cpython-38.pyc and b/dist/ba_data/python/bastd/ui/__pycache__/getremote.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/getremote.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/getremote.cpython-39.opt-1.pyc new file mode 100644 index 0000000..d5efdf8 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/getremote.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/getremote.cpython-39.pyc b/dist/ba_data/python/bastd/ui/__pycache__/getremote.cpython-39.pyc new file mode 100644 index 0000000..6b56e66 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/getremote.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/helpui.cpython-38.pyc b/dist/ba_data/python/bastd/ui/__pycache__/helpui.cpython-38.pyc index 40fea6c..1be7a19 100644 Binary files a/dist/ba_data/python/bastd/ui/__pycache__/helpui.cpython-38.pyc and b/dist/ba_data/python/bastd/ui/__pycache__/helpui.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/helpui.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/helpui.cpython-39.opt-1.pyc new file mode 100644 index 0000000..34fa4a4 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/helpui.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/helpui.cpython-39.pyc b/dist/ba_data/python/bastd/ui/__pycache__/helpui.cpython-39.pyc new file mode 100644 index 0000000..8fdfac5 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/helpui.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/iconpicker.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/iconpicker.cpython-39.opt-1.pyc new file mode 100644 index 0000000..0c6f936 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/iconpicker.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/kiosk.cpython-38.pyc b/dist/ba_data/python/bastd/ui/__pycache__/kiosk.cpython-38.pyc index d02ed72..3848014 100644 Binary files a/dist/ba_data/python/bastd/ui/__pycache__/kiosk.cpython-38.pyc and b/dist/ba_data/python/bastd/ui/__pycache__/kiosk.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/kiosk.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/kiosk.cpython-39.opt-1.pyc new file mode 100644 index 0000000..2566d67 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/kiosk.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/kiosk.cpython-39.pyc b/dist/ba_data/python/bastd/ui/__pycache__/kiosk.cpython-39.pyc new file mode 100644 index 0000000..d0c8549 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/kiosk.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/mainmenu.cpython-38.pyc b/dist/ba_data/python/bastd/ui/__pycache__/mainmenu.cpython-38.pyc index 4b87df3..b360a1f 100644 Binary files a/dist/ba_data/python/bastd/ui/__pycache__/mainmenu.cpython-38.pyc and b/dist/ba_data/python/bastd/ui/__pycache__/mainmenu.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/mainmenu.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/mainmenu.cpython-39.opt-1.pyc new file mode 100644 index 0000000..1c54c42 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/mainmenu.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/mainmenu.cpython-39.pyc b/dist/ba_data/python/bastd/ui/__pycache__/mainmenu.cpython-39.pyc new file mode 100644 index 0000000..c9c9fcc Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/mainmenu.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/onscreenkeyboard.cpython-38.pyc b/dist/ba_data/python/bastd/ui/__pycache__/onscreenkeyboard.cpython-38.pyc index 7e92432..5c5618c 100644 Binary files a/dist/ba_data/python/bastd/ui/__pycache__/onscreenkeyboard.cpython-38.pyc and b/dist/ba_data/python/bastd/ui/__pycache__/onscreenkeyboard.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/onscreenkeyboard.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/onscreenkeyboard.cpython-39.opt-1.pyc new file mode 100644 index 0000000..f9874c3 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/onscreenkeyboard.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/onscreenkeyboard.cpython-39.pyc b/dist/ba_data/python/bastd/ui/__pycache__/onscreenkeyboard.cpython-39.pyc new file mode 100644 index 0000000..ecef9a3 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/onscreenkeyboard.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/party.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/party.cpython-38.opt-1.pyc index 29b5260..f5861ca 100644 Binary files a/dist/ba_data/python/bastd/ui/__pycache__/party.cpython-38.opt-1.pyc and b/dist/ba_data/python/bastd/ui/__pycache__/party.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/party.cpython-38.pyc b/dist/ba_data/python/bastd/ui/__pycache__/party.cpython-38.pyc index 2cf0b9f..f074d12 100644 Binary files a/dist/ba_data/python/bastd/ui/__pycache__/party.cpython-38.pyc and b/dist/ba_data/python/bastd/ui/__pycache__/party.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/party.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/party.cpython-39.opt-1.pyc new file mode 100644 index 0000000..af87a09 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/party.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/party.cpython-39.pyc b/dist/ba_data/python/bastd/ui/__pycache__/party.cpython-39.pyc new file mode 100644 index 0000000..4394a2d Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/party.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/partyqueue.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/partyqueue.cpython-39.opt-1.pyc new file mode 100644 index 0000000..30d1569 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/partyqueue.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/play.cpython-38.pyc b/dist/ba_data/python/bastd/ui/__pycache__/play.cpython-38.pyc index 069acb5..a1f34e3 100644 Binary files a/dist/ba_data/python/bastd/ui/__pycache__/play.cpython-38.pyc and b/dist/ba_data/python/bastd/ui/__pycache__/play.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/play.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/play.cpython-39.opt-1.pyc new file mode 100644 index 0000000..f990001 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/play.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/play.cpython-39.pyc b/dist/ba_data/python/bastd/ui/__pycache__/play.cpython-39.pyc new file mode 100644 index 0000000..2695f64 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/play.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/playoptions.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/playoptions.cpython-39.opt-1.pyc new file mode 100644 index 0000000..fffdeee Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/playoptions.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/popup.cpython-38.pyc b/dist/ba_data/python/bastd/ui/__pycache__/popup.cpython-38.pyc index 7f29a08..0457e01 100644 Binary files a/dist/ba_data/python/bastd/ui/__pycache__/popup.cpython-38.pyc and b/dist/ba_data/python/bastd/ui/__pycache__/popup.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/popup.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/popup.cpython-39.opt-1.pyc new file mode 100644 index 0000000..0284e95 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/popup.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/popup.cpython-39.pyc b/dist/ba_data/python/bastd/ui/__pycache__/popup.cpython-39.pyc new file mode 100644 index 0000000..f6a15e8 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/popup.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/promocode.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/promocode.cpython-39.opt-1.pyc new file mode 100644 index 0000000..07de1aa Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/promocode.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/purchase.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/purchase.cpython-39.opt-1.pyc new file mode 100644 index 0000000..0d9d0b4 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/purchase.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/qrcode.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/qrcode.cpython-39.opt-1.pyc new file mode 100644 index 0000000..56c8ddb Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/qrcode.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/radiogroup.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/radiogroup.cpython-39.opt-1.pyc new file mode 100644 index 0000000..11fe8c2 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/radiogroup.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/report.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/report.cpython-38.opt-1.pyc index d6a6258..fb7fee9 100644 Binary files a/dist/ba_data/python/bastd/ui/__pycache__/report.cpython-38.opt-1.pyc and b/dist/ba_data/python/bastd/ui/__pycache__/report.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/report.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/report.cpython-39.opt-1.pyc new file mode 100644 index 0000000..52e5ffd Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/report.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/resourcetypeinfo.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/resourcetypeinfo.cpython-39.opt-1.pyc new file mode 100644 index 0000000..03ec131 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/resourcetypeinfo.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/serverdialog.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/serverdialog.cpython-39.opt-1.pyc new file mode 100644 index 0000000..c8eb5c2 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/serverdialog.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/specialoffer.cpython-38.pyc b/dist/ba_data/python/bastd/ui/__pycache__/specialoffer.cpython-38.pyc index fca7a33..33b838c 100644 Binary files a/dist/ba_data/python/bastd/ui/__pycache__/specialoffer.cpython-38.pyc and b/dist/ba_data/python/bastd/ui/__pycache__/specialoffer.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/specialoffer.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/specialoffer.cpython-39.opt-1.pyc new file mode 100644 index 0000000..70d62ad Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/specialoffer.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/specialoffer.cpython-39.pyc b/dist/ba_data/python/bastd/ui/__pycache__/specialoffer.cpython-39.pyc new file mode 100644 index 0000000..432f21a Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/specialoffer.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/tabs.cpython-38.pyc b/dist/ba_data/python/bastd/ui/__pycache__/tabs.cpython-38.pyc index 7e9e500..abfdf83 100644 Binary files a/dist/ba_data/python/bastd/ui/__pycache__/tabs.cpython-38.pyc and b/dist/ba_data/python/bastd/ui/__pycache__/tabs.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/tabs.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/tabs.cpython-39.opt-1.pyc new file mode 100644 index 0000000..0efc86e Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/tabs.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/tabs.cpython-39.pyc b/dist/ba_data/python/bastd/ui/__pycache__/tabs.cpython-39.pyc new file mode 100644 index 0000000..1ff9ab4 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/tabs.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/teamnamescolors.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/teamnamescolors.cpython-39.opt-1.pyc new file mode 100644 index 0000000..4249448 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/teamnamescolors.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/telnet.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/telnet.cpython-39.opt-1.pyc new file mode 100644 index 0000000..0ef3ce0 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/telnet.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/tournamententry.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/tournamententry.cpython-38.opt-1.pyc index b47440c..e92d108 100644 Binary files a/dist/ba_data/python/bastd/ui/__pycache__/tournamententry.cpython-38.opt-1.pyc and b/dist/ba_data/python/bastd/ui/__pycache__/tournamententry.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/tournamententry.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/tournamententry.cpython-39.opt-1.pyc new file mode 100644 index 0000000..6efaa0c Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/tournamententry.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/tournamentscores.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/tournamentscores.cpython-39.opt-1.pyc new file mode 100644 index 0000000..93ad55e Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/tournamentscores.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/trophies.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/trophies.cpython-39.opt-1.pyc new file mode 100644 index 0000000..1d2b566 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/trophies.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/url.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/url.cpython-39.opt-1.pyc new file mode 100644 index 0000000..f3c602f Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/url.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/watch.cpython-38.pyc b/dist/ba_data/python/bastd/ui/__pycache__/watch.cpython-38.pyc index 20af930..5209303 100644 Binary files a/dist/ba_data/python/bastd/ui/__pycache__/watch.cpython-38.pyc and b/dist/ba_data/python/bastd/ui/__pycache__/watch.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/watch.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/__pycache__/watch.cpython-39.opt-1.pyc new file mode 100644 index 0000000..43830b4 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/watch.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/__pycache__/watch.cpython-39.pyc b/dist/ba_data/python/bastd/ui/__pycache__/watch.cpython-39.pyc new file mode 100644 index 0000000..5d21a0e Binary files /dev/null and b/dist/ba_data/python/bastd/ui/__pycache__/watch.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/ui/account/__pycache__/__init__.cpython-38.pyc b/dist/ba_data/python/bastd/ui/account/__pycache__/__init__.cpython-38.pyc index a604f55..c556c9f 100644 Binary files a/dist/ba_data/python/bastd/ui/account/__pycache__/__init__.cpython-38.pyc and b/dist/ba_data/python/bastd/ui/account/__pycache__/__init__.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/ui/account/__pycache__/__init__.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/account/__pycache__/__init__.cpython-39.opt-1.pyc new file mode 100644 index 0000000..7db30b9 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/account/__pycache__/__init__.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/account/__pycache__/__init__.cpython-39.pyc b/dist/ba_data/python/bastd/ui/account/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..e2e319e Binary files /dev/null and b/dist/ba_data/python/bastd/ui/account/__pycache__/__init__.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/ui/account/__pycache__/link.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/account/__pycache__/link.cpython-39.opt-1.pyc new file mode 100644 index 0000000..c0e335d Binary files /dev/null and b/dist/ba_data/python/bastd/ui/account/__pycache__/link.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/account/__pycache__/settings.cpython-38.pyc b/dist/ba_data/python/bastd/ui/account/__pycache__/settings.cpython-38.pyc index 29392fd..40900d8 100644 Binary files a/dist/ba_data/python/bastd/ui/account/__pycache__/settings.cpython-38.pyc and b/dist/ba_data/python/bastd/ui/account/__pycache__/settings.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/ui/account/__pycache__/settings.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/account/__pycache__/settings.cpython-39.opt-1.pyc new file mode 100644 index 0000000..7f1aad9 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/account/__pycache__/settings.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/account/__pycache__/settings.cpython-39.pyc b/dist/ba_data/python/bastd/ui/account/__pycache__/settings.cpython-39.pyc new file mode 100644 index 0000000..697e4f2 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/account/__pycache__/settings.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/ui/account/__pycache__/unlink.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/account/__pycache__/unlink.cpython-39.opt-1.pyc new file mode 100644 index 0000000..a35cbfa Binary files /dev/null and b/dist/ba_data/python/bastd/ui/account/__pycache__/unlink.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/account/__pycache__/viewer.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/account/__pycache__/viewer.cpython-39.opt-1.pyc new file mode 100644 index 0000000..68a60f6 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/account/__pycache__/viewer.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/account/link.py b/dist/ba_data/python/bastd/ui/account/link.py index f82985b..df01fc9 100644 --- a/dist/ba_data/python/bastd/ui/account/link.py +++ b/dist/ba_data/python/bastd/ui/account/link.py @@ -12,14 +12,14 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Tuple, Optional, Dict + from typing import Any, Optional class AccountLinkWindow(ba.Window): """Window for linking accounts.""" def __init__(self, origin_widget: ba.Widget = None): - scale_origin: Optional[Tuple[float, float]] + scale_origin: Optional[tuple[float, float]] if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() @@ -111,7 +111,7 @@ class AccountLinkWindow(ba.Window): class AccountLinkCodeWindow(ba.Window): """Window showing code for account-linking.""" - def __init__(self, data: Dict[str, Any]): + def __init__(self, data: dict[str, Any]): self._width = 350 self._height = 200 uiscale = ba.app.ui.uiscale diff --git a/dist/ba_data/python/bastd/ui/account/settings.py b/dist/ba_data/python/bastd/ui/account/settings.py index 785f186..3e517b1 100644 --- a/dist/ba_data/python/bastd/ui/account/settings.py +++ b/dist/ba_data/python/bastd/ui/account/settings.py @@ -12,7 +12,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Optional, Tuple, List, Union + from typing import Optional, Union class AccountSettingsWindow(ba.Window): @@ -29,7 +29,7 @@ class AccountSettingsWindow(ba.Window): ba.set_analytics_screen('Account Window') # If they provided an origin-widget, scale up from that. - scale_origin: Optional[Tuple[float, float]] + scale_origin: Optional[tuple[float, float]] if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() @@ -74,7 +74,7 @@ class AccountSettingsWindow(ba.Window): self._sub_width = self._scroll_width - 20 # Determine which sign-in/sign-out buttons we should show. - self._show_sign_in_buttons: List[str] = [] + self._show_sign_in_buttons: list[str] = [] if app.platform == 'android' and app.subplatform == 'google': self._show_sign_in_buttons.append('Google Play') diff --git a/dist/ba_data/python/bastd/ui/account/unlink.py b/dist/ba_data/python/bastd/ui/account/unlink.py index ef3b011..8ef0451 100644 --- a/dist/ba_data/python/bastd/ui/account/unlink.py +++ b/dist/ba_data/python/bastd/ui/account/unlink.py @@ -11,14 +11,14 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Optional, Tuple, Dict + from typing import Any, Optional class AccountUnlinkWindow(ba.Window): """A window to kick off account unlinks.""" def __init__(self, origin_widget: ba.Widget = None): - scale_origin: Optional[Tuple[float, float]] + scale_origin: Optional[tuple[float, float]] if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() @@ -104,7 +104,7 @@ class AccountUnlinkWindow(ba.Window): if i == 0: ba.widget(edit=txt, up_widget=self._cancel_button) - def _on_entry_selected(self, entry: Dict[str, Any]) -> None: + def _on_entry_selected(self, entry: dict[str, Any]) -> None: ba.screenmessage(ba.Lstr(resource='pleaseWaitText', fallback_resource='requestingText'), color=(0, 1, 0)) diff --git a/dist/ba_data/python/bastd/ui/account/viewer.py b/dist/ba_data/python/bastd/ui/account/viewer.py index a8ea39a..01f18c1 100644 --- a/dist/ba_data/python/bastd/ui/account/viewer.py +++ b/dist/ba_data/python/bastd/ui/account/viewer.py @@ -11,7 +11,7 @@ import ba from bastd.ui import popup if TYPE_CHECKING: - from typing import Any, Tuple, Dict, Optional + from typing import Any, Optional class AccountViewerWindow(popup.PopupWindow): @@ -20,9 +20,9 @@ class AccountViewerWindow(popup.PopupWindow): def __init__(self, account_id: str, profile_id: str = None, - position: Tuple[float, float] = (0.0, 0.0), + position: tuple[float, float] = (0.0, 0.0), scale: float = None, - offset: Tuple[float, float] = (0.0, 0.0)): + offset: tuple[float, float] = (0.0, 0.0)): from ba.internal import is_browser_likely_available, master_server_get self._account_id = account_id @@ -169,7 +169,7 @@ class AccountViewerWindow(popup.PopupWindow): ba.open_url(_ba.get_master_server_address() + '/highscores?profile=' + self._account_id) - def _on_query_response(self, data: Optional[Dict[str, Any]]) -> None: + def _on_query_response(self, data: Optional[dict[str, Any]]) -> None: # FIXME: Tidy this up. # pylint: disable=too-many-locals # pylint: disable=too-many-branches diff --git a/dist/ba_data/python/bastd/ui/achievements.py b/dist/ba_data/python/bastd/ui/achievements.py index ca56c43..5ca7d51 100644 --- a/dist/ba_data/python/bastd/ui/achievements.py +++ b/dist/ba_data/python/bastd/ui/achievements.py @@ -10,13 +10,13 @@ import ba from bastd.ui import popup if TYPE_CHECKING: - from typing import Tuple + pass class AchievementsWindow(popup.PopupWindow): """Popup window to view achievements.""" - def __init__(self, position: Tuple[float, float], scale: float = None): + def __init__(self, position: tuple[float, float], scale: float = None): # pylint: disable=too-many-locals uiscale = ba.app.ui.uiscale if scale is None: diff --git a/dist/ba_data/python/bastd/ui/appinvite.py b/dist/ba_data/python/bastd/ui/appinvite.py index c058f76..9d3cb38 100644 --- a/dist/ba_data/python/bastd/ui/appinvite.py +++ b/dist/ba_data/python/bastd/ui/appinvite.py @@ -12,7 +12,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Optional, Dict, Union + from typing import Any, Optional, Union class AppInviteWindow(ba.Window): @@ -20,7 +20,7 @@ class AppInviteWindow(ba.Window): def __init__(self) -> None: ba.set_analytics_screen('AppInviteWindow') - self._data: Optional[Dict[str, Any]] = None + self._data: Optional[dict[str, Any]] = None self._width = 650 self._height = 400 @@ -114,7 +114,7 @@ class AppInviteWindow(ba.Window): callback=ba.WeakCall(self._on_code_result)) _ba.run_transactions() - def _on_code_result(self, result: Optional[Dict[str, Any]]) -> None: + def _on_code_result(self, result: Optional[dict[str, Any]]) -> None: if result is not None: self._data = result @@ -151,7 +151,7 @@ class AppInviteWindow(ba.Window): class ShowFriendCodeWindow(ba.Window): """Window showing a code for sharing with friends.""" - def __init__(self, data: Dict[str, Any]): + 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 @@ -317,7 +317,7 @@ def handle_app_invites_press(force_code: bool = False) -> None: ba.Lstr(resource='gatherWindow.requestingAPromoCodeText'), color=(0, 1, 0)) - def handle_result(result: Optional[Dict[str, Any]]) -> None: + def handle_result(result: Optional[dict[str, Any]]) -> None: with ba.Context('ui'): if result is None: ba.screenmessage(ba.Lstr(resource='errorText'), diff --git a/dist/ba_data/python/bastd/ui/characterpicker.py b/dist/ba_data/python/bastd/ui/characterpicker.py index 6f57a20..b60a476 100644 --- a/dist/ba_data/python/bastd/ui/characterpicker.py +++ b/dist/ba_data/python/bastd/ui/characterpicker.py @@ -12,7 +12,7 @@ import ba from bastd.ui import popup if TYPE_CHECKING: - from typing import Any, Tuple, Sequence + from typing import Any, Sequence class CharacterPicker(popup.PopupWindow): @@ -20,10 +20,10 @@ class CharacterPicker(popup.PopupWindow): def __init__(self, parent: ba.Widget, - position: Tuple[float, float] = (0.0, 0.0), + position: tuple[float, float] = (0.0, 0.0), delegate: Any = None, scale: float = None, - offset: Tuple[float, float] = (0.0, 0.0), + 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): diff --git a/dist/ba_data/python/bastd/ui/colorpicker.py b/dist/ba_data/python/bastd/ui/colorpicker.py index b9630a2..14e935c 100644 --- a/dist/ba_data/python/bastd/ui/colorpicker.py +++ b/dist/ba_data/python/bastd/ui/colorpicker.py @@ -10,7 +10,7 @@ import ba from bastd.ui.popup import PopupWindow if TYPE_CHECKING: - from typing import Any, Tuple, Sequence, List, Optional + from typing import Any, Sequence, Optional class ColorPicker(PopupWindow): @@ -21,11 +21,11 @@ class ColorPicker(PopupWindow): def __init__(self, parent: ba.Widget, - position: Tuple[float, float], + position: tuple[float, float], initial_color: Sequence[float] = (1.0, 1.0, 1.0), delegate: Any = None, scale: float = None, - offset: Tuple[float, float] = (0.0, 0.0), + offset: tuple[float, float] = (0.0, 0.0), tag: Any = ''): # pylint: disable=too-many-locals from ba.internal import get_player_colors @@ -56,11 +56,11 @@ class ColorPicker(PopupWindow): focus_size=(190, 220), bg_color=(0.5, 0.5, 0.5), offset=offset) - rows: List[List[ba.Widget]] = [] + rows: list[list[ba.Widget]] = [] closest_dist = 9999.0 closest = (0, 0) for y in range(4): - row: List[ba.Widget] = [] + row: list[ba.Widget] = [] rows.append(row) for x in range(4): color = self.colors[y][x] @@ -159,11 +159,11 @@ class ColorPickerExact(PopupWindow): def __init__(self, parent: ba.Widget, - position: Tuple[float, float], + position: tuple[float, float], initial_color: Sequence[float] = (1.0, 1.0, 1.0), delegate: Any = None, scale: float = None, - offset: Tuple[float, float] = (0.0, 0.0), + offset: tuple[float, float] = (0.0, 0.0), tag: Any = ''): # pylint: disable=too-many-locals del parent # Unused var. @@ -256,6 +256,7 @@ class ColorPickerExact(PopupWindow): # We generate these procedurally, so pylint misses them. # FIXME: create static attrs instead. + # pylint: disable=consider-using-f-string ba.textwidget(edit=self._label_r, text='%.2f' % self._color[0]) ba.textwidget(edit=self._label_g, text='%.2f' % self._color[1]) ba.textwidget(edit=self._label_b, text='%.2f' % self._color[2]) diff --git a/dist/ba_data/python/bastd/ui/config.py b/dist/ba_data/python/bastd/ui/config.py index 0c11ff4..b9e7400 100644 --- a/dist/ba_data/python/bastd/ui/config.py +++ b/dist/ba_data/python/bastd/ui/config.py @@ -9,7 +9,7 @@ from typing import TYPE_CHECKING import ba if TYPE_CHECKING: - from typing import Any, Tuple, Union, Callable + from typing import Any, Union, Callable class ConfigCheckBox: @@ -27,8 +27,8 @@ class ConfigCheckBox: def __init__(self, parent: ba.Widget, configkey: str, - position: Tuple[float, float], - size: Tuple[float, float], + position: tuple[float, float], + size: tuple[float, float], displayname: Union[str, ba.Lstr] = None, scale: float = None, maxwidth: float = None, @@ -84,7 +84,7 @@ class ConfigNumberEdit: def __init__(self, parent: ba.Widget, configkey: str, - position: Tuple[float, float], + position: tuple[float, float], minval: float = 0.0, maxval: float = 100.0, increment: float = 1.0, diff --git a/dist/ba_data/python/bastd/ui/confirm.py b/dist/ba_data/python/bastd/ui/confirm.py index 82e2ac4..37ed99a 100644 --- a/dist/ba_data/python/bastd/ui/confirm.py +++ b/dist/ba_data/python/bastd/ui/confirm.py @@ -10,7 +10,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Union, Callable, Tuple, Optional + from typing import Any, Union, Callable, Optional class ConfirmWindow: @@ -23,7 +23,7 @@ class ConfirmWindow: height: float = 100.0, cancel_button: bool = True, cancel_is_selected: bool = False, - color: Tuple[float, float, float] = (1, 1, 1), + color: tuple[float, float, float] = (1, 1, 1), text_scale: float = 1.0, ok_text: Union[str, ba.Lstr] = None, cancel_text: Union[str, ba.Lstr] = None, @@ -39,7 +39,7 @@ class ConfirmWindow: # if they provided an origin-widget, scale up from that self._transition_out: Optional[str] - scale_origin: Optional[Tuple[float, float]] + scale_origin: Optional[tuple[float, float]] if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() diff --git a/dist/ba_data/python/bastd/ui/coop/__pycache__/__init__.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/coop/__pycache__/__init__.cpython-39.opt-1.pyc new file mode 100644 index 0000000..de6c0cc Binary files /dev/null and b/dist/ba_data/python/bastd/ui/coop/__pycache__/__init__.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/coop/__pycache__/browser.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/ui/coop/__pycache__/browser.cpython-38.opt-1.pyc index 34453c4..642c705 100644 Binary files a/dist/ba_data/python/bastd/ui/coop/__pycache__/browser.cpython-38.opt-1.pyc and b/dist/ba_data/python/bastd/ui/coop/__pycache__/browser.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/coop/__pycache__/browser.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/coop/__pycache__/browser.cpython-39.opt-1.pyc new file mode 100644 index 0000000..5f07c33 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/coop/__pycache__/browser.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/coop/__pycache__/gamebutton.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/coop/__pycache__/gamebutton.cpython-39.opt-1.pyc new file mode 100644 index 0000000..9035862 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/coop/__pycache__/gamebutton.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/coop/__pycache__/level.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/coop/__pycache__/level.cpython-39.opt-1.pyc new file mode 100644 index 0000000..fe46490 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/coop/__pycache__/level.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/coop/browser.py b/dist/ba_data/python/bastd/ui/coop/browser.py index c8d0eee..6fb34fe 100644 --- a/dist/ba_data/python/bastd/ui/coop/browser.py +++ b/dist/ba_data/python/bastd/ui/coop/browser.py @@ -16,7 +16,7 @@ from bastd.ui.league.rankbutton import LeagueRankButton from bastd.ui.store.browser import StoreBrowserWindow if TYPE_CHECKING: - from typing import Any, Optional, Tuple, Dict, List, Union + from typing import Any, Optional, Union class CoopBrowserWindow(ba.Window): @@ -62,7 +62,7 @@ class CoopBrowserWindow(ba.Window): timetype=ba.TimeType.REAL) # If they provided an origin-widget, scale up from that. - scale_origin: Optional[Tuple[float, float]] + scale_origin: Optional[tuple[float, float]] if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() @@ -361,7 +361,7 @@ class CoopBrowserWindow(ba.Window): except Exception: ba.print_exception('Error updating campaign lock.') - def _update_for_data(self, data: Optional[List[Dict[str, Any]]]) -> None: + def _update_for_data(self, data: Optional[list[dict[str, Any]]]) -> None: # pylint: disable=too-many-statements # pylint: disable=too-many-locals # pylint: disable=too-many-branches @@ -369,9 +369,9 @@ 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)))): + 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 @@ -380,7 +380,7 @@ class CoopBrowserWindow(ba.Window): # Update all of our tourney buttons based on whats in data. for i, tbtn in enumerate(self._tournament_buttons): assert data is not None - entry: Dict[str, Any] = data[i] + entry: dict[str, Any] = data[i] prize_y_offs = (34 if 'prizeRange3' in entry else 20 if 'prizeRange2' in entry else 12) x_offs = 90 @@ -589,7 +589,7 @@ class CoopBrowserWindow(ba.Window): ('' + str(free_tries_remaining))), color=(0.6, 0.6, 0.6, 1)) - def _on_tournament_query_response(self, data: Optional[Dict[str, + def _on_tournament_query_response(self, data: Optional[dict[str, Any]]) -> None: accounts = ba.app.accounts if data is not None: @@ -835,7 +835,7 @@ class CoopBrowserWindow(ba.Window): # Tournaments - self._tournament_buttons: List[Dict[str, Any]] = [] + self._tournament_buttons: list[dict[str, Any]] = [] v -= 53 # FIXME shouldn't use hard-coded strings here. @@ -976,7 +976,7 @@ class CoopBrowserWindow(ba.Window): 30 + 200 * len(items)), 200), background=False) h_spacing = 200 - self._custom_buttons: List[GameButton] = [] + self._custom_buttons: list[GameButton] = [] h = 0 v2 = -2 for item in items: @@ -989,22 +989,21 @@ class CoopBrowserWindow(ba.Window): # (for wiring up) self._refresh_campaign_row() - for i in range(len(self._tournament_buttons)): + for i, tbutton in enumerate(self._tournament_buttons): ba.widget( - edit=self._tournament_buttons[i]['button'], + edit=tbutton['button'], up_widget=self._tournament_info_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) ba.widget( - edit=self._tournament_buttons[i]['more_scores_button'], + 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=self._tournament_buttons[i]['current_leader_name_text'], - up_widget=self._tournament_info_button if i == 0 else - self._tournament_buttons[i - 1]['more_scores_button']) + 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: @@ -1035,10 +1034,10 @@ class CoopBrowserWindow(ba.Window): self._do_selection_callbacks = True def _tournament_button(self, parent: ba.Widget, x: float, y: float, - select: bool) -> Dict[str, Any]: + select: bool) -> dict[str, Any]: sclx = 300 scly = 195.0 - data: Dict[str, Any] = { + data: dict[str, Any] = { 'tournament_id': None, 'time_remaining': 0, 'has_time_remaining': False, @@ -1375,7 +1374,7 @@ class CoopBrowserWindow(ba.Window): show_tab=show_tab, back_location='CoopBrowserWindow').get_root_widget()) - def _show_leader(self, tournament_button: Dict[str, Any]) -> None: + def _show_leader(self, tournament_button: dict[str, Any]) -> None: # pylint: disable=cyclic-import from bastd.ui.account.viewer import AccountViewerWindow tournament_id = tournament_button['tournament_id'] @@ -1393,7 +1392,7 @@ class CoopBrowserWindow(ba.Window): position=tournament_button['current_leader_name_text']. get_screen_space_center()) - def _show_scores(self, tournament_button: Dict[str, Any]) -> None: + def _show_scores(self, tournament_button: dict[str, Any]) -> None: # pylint: disable=cyclic-import from bastd.ui.tournamentscores import TournamentScoresWindow tournament_id = tournament_button['tournament_id'] @@ -1412,7 +1411,7 @@ class CoopBrowserWindow(ba.Window): def run(self, game: Optional[str], - tournament_button: Dict[str, Any] = None) -> None: + tournament_button: dict[str, Any] = None) -> None: """Run the provided game.""" # pylint: disable=too-many-branches # pylint: disable=too-many-statements @@ -1422,7 +1421,7 @@ class CoopBrowserWindow(ba.Window): from bastd.ui.tournamententry import TournamentEntryWindow from bastd.ui.purchase import PurchaseWindow from bastd.ui.account import show_sign_in_prompt - args: Dict[str, Any] = {} + args: dict[str, Any] = {} # Do a bit of pre-flight for tournament options. if tournament_button is not None: diff --git a/dist/ba_data/python/bastd/ui/coop/gamebutton.py b/dist/ba_data/python/bastd/ui/coop/gamebutton.py index 8aee88b..2a6a75b 100644 --- a/dist/ba_data/python/bastd/ui/coop/gamebutton.py +++ b/dist/ba_data/python/bastd/ui/coop/gamebutton.py @@ -11,7 +11,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Optional, List, Tuple + from typing import Optional from bastd.ui.coop.browser import CoopBrowserWindow @@ -98,7 +98,7 @@ class GameButton: starscale = 35.0 if self._achievements else 45.0 - self._star_widgets: List[ba.Widget] = [] + self._star_widgets: list[ba.Widget] = [] for _i in range(stars): imw = ba.imagewidget(parent=parent, draw_controller=btn, @@ -120,7 +120,7 @@ class GameButton: xach = x + 69 yach = y + scly - 168 a_scale = 30.0 - self._achievement_widgets: List[Tuple[ba.Widget, ba.Widget]] = [] + self._achievement_widgets: list[tuple[ba.Widget, ba.Widget]] = [] for ach in self._achievements: a_complete = ach.complete imw = ba.imagewidget( diff --git a/dist/ba_data/python/bastd/ui/creditslist.py b/dist/ba_data/python/bastd/ui/creditslist.py index b5caebc..d6a37c8 100644 --- a/dist/ba_data/python/bastd/ui/creditslist.py +++ b/dist/ba_data/python/bastd/ui/creditslist.py @@ -10,7 +10,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Tuple, Optional, Sequence + from typing import Optional, Sequence class CreditsListWindow(ba.Window): @@ -23,7 +23,7 @@ class CreditsListWindow(ba.Window): ba.set_analytics_screen('Credits Window') # if they provided an origin-widget, scale up from that - scale_origin: Optional[Tuple[float, float]] + scale_origin: Optional[tuple[float, float]] if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() @@ -156,7 +156,8 @@ class CreditsListWindow(ba.Window): freesound_names = _format_names(names, 90) try: - with open('ba_data/data/langdata.json') as infile: + with open('ba_data/data/langdata.json', + encoding='utf-8') as infile: translation_contributors = (json.loads( infile.read())['translation_contributors']) except Exception: diff --git a/dist/ba_data/python/bastd/ui/fileselector.py b/dist/ba_data/python/bastd/ui/fileselector.py index da6e2b2..e48b456 100644 --- a/dist/ba_data/python/bastd/ui/fileselector.py +++ b/dist/ba_data/python/bastd/ui/fileselector.py @@ -13,7 +13,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Callable, Sequence, List, Optional + from typing import Any, Callable, Sequence, Optional class FileSelectorWindow(ba.Window): @@ -34,7 +34,7 @@ class FileSelectorWindow(ba.Window): self._callback = callback self._base_path = path self._path: Optional[str] = None - self._recent_paths: List[str] = [] + self._recent_paths: list[str] = [] self._show_base_path = show_base_path self._valid_file_extensions = [ '.' + ext for ext in valid_file_extensions @@ -173,7 +173,7 @@ class FileSelectorWindow(ba.Window): class _RefreshThread(threading.Thread): def __init__(self, path: str, - callback: Callable[[List[str], Optional[str]], Any]): + callback: Callable[[list[str], Optional[str]], Any]): super().__init__() self._callback = callback self._path = path @@ -195,7 +195,7 @@ class FileSelectorWindow(ba.Window): # Ignore permission-denied. if 'Errno 13' not in str(exc): ba.print_exception() - nofiles: List[str] = [] + nofiles: list[str] = [] ba.pushcall(ba.Call(self._callback, nofiles, str(exc)), from_other_thread=True) @@ -205,7 +205,7 @@ class FileSelectorWindow(ba.Window): self._recent_paths.append(path) self._RefreshThread(path, self._refresh).start() - def _refresh(self, file_names: List[str], error: Optional[str]) -> None: + def _refresh(self, file_names: list[str], error: Optional[str]) -> None: # pylint: disable=too-many-statements # pylint: disable=too-many-branches # pylint: disable=too-many-locals diff --git a/dist/ba_data/python/bastd/ui/gather/__init__.py b/dist/ba_data/python/bastd/ui/gather/__init__.py index 3d21747..8dca6cc 100644 --- a/dist/ba_data/python/bastd/ui/gather/__init__.py +++ b/dist/ba_data/python/bastd/ui/gather/__init__.py @@ -13,8 +13,7 @@ import ba from bastd.ui.tabs import TabRow if TYPE_CHECKING: - from typing import (Any, Optional, Tuple, Dict, List, Union, Callable, - Type) + from typing import Optional class GatherTab: @@ -80,7 +79,7 @@ class GatherWindow(ba.Window): from bastd.ui.gather.nearbytab import NearbyGatherTab ba.set_analytics_screen('Gather Window') - scale_origin: Optional[Tuple[float, float]] + scale_origin: Optional[tuple[float, float]] if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() @@ -149,7 +148,7 @@ class GatherWindow(ba.Window): 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]] = [ + tabdefs: list[tuple[GatherWindow.TabID, ba.Lstr]] = [ (self.TabID.ABOUT, ba.Lstr(resource=self._r + '.aboutText')) ] if _ba.get_account_misc_read_val('enablePublicParties', True): @@ -173,14 +172,14 @@ class GatherWindow(ba.Window): on_select_call=ba.WeakCall(self._set_tab)) # Now instantiate handlers for these tabs. - tabtypes: Dict[GatherWindow.TabID, Type[GatherTab]] = { + tabtypes: dict[GatherWindow.TabID, type[GatherTab]] = { self.TabID.ABOUT: AboutGatherTab, self.TabID.MANUAL: ManualGatherTab, self.TabID.PRIVATE: PrivateGatherTab, self.TabID.INTERNET: PublicGatherTab, self.TabID.NEARBY: NearbyGatherTab } - self._tabs: Dict[GatherWindow.TabID, GatherTab] = {} + self._tabs: dict[GatherWindow.TabID, GatherTab] = {} for tab_id in self._tab_row.tabs: tabtype = tabtypes.get(tab_id) if tabtype is not None: diff --git a/dist/ba_data/python/bastd/ui/gather/__pycache__/__init__.cpython-38.pyc b/dist/ba_data/python/bastd/ui/gather/__pycache__/__init__.cpython-38.pyc index 33dbdb2..1f7a7fb 100644 Binary files a/dist/ba_data/python/bastd/ui/gather/__pycache__/__init__.cpython-38.pyc and b/dist/ba_data/python/bastd/ui/gather/__pycache__/__init__.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/ui/gather/__pycache__/__init__.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/gather/__pycache__/__init__.cpython-39.opt-1.pyc new file mode 100644 index 0000000..5b9407b Binary files /dev/null and b/dist/ba_data/python/bastd/ui/gather/__pycache__/__init__.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/gather/__pycache__/__init__.cpython-39.pyc b/dist/ba_data/python/bastd/ui/gather/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..0df1d90 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/gather/__pycache__/__init__.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/ui/gather/__pycache__/abouttab.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/gather/__pycache__/abouttab.cpython-39.opt-1.pyc new file mode 100644 index 0000000..1c6f1be Binary files /dev/null and b/dist/ba_data/python/bastd/ui/gather/__pycache__/abouttab.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/gather/__pycache__/manualtab.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/ui/gather/__pycache__/manualtab.cpython-38.opt-1.pyc index 8b048c3..bfa6fd3 100644 Binary files a/dist/ba_data/python/bastd/ui/gather/__pycache__/manualtab.cpython-38.opt-1.pyc and b/dist/ba_data/python/bastd/ui/gather/__pycache__/manualtab.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/gather/__pycache__/manualtab.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/gather/__pycache__/manualtab.cpython-39.opt-1.pyc new file mode 100644 index 0000000..b3d6799 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/gather/__pycache__/manualtab.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/gather/__pycache__/nearbytab.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/gather/__pycache__/nearbytab.cpython-39.opt-1.pyc new file mode 100644 index 0000000..a942c3e Binary files /dev/null and b/dist/ba_data/python/bastd/ui/gather/__pycache__/nearbytab.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/gather/__pycache__/privatetab.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/gather/__pycache__/privatetab.cpython-39.opt-1.pyc new file mode 100644 index 0000000..b939277 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/gather/__pycache__/privatetab.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/gather/__pycache__/publictab.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/ui/gather/__pycache__/publictab.cpython-38.opt-1.pyc index 71f3e0f..de63219 100644 Binary files a/dist/ba_data/python/bastd/ui/gather/__pycache__/publictab.cpython-38.opt-1.pyc and b/dist/ba_data/python/bastd/ui/gather/__pycache__/publictab.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/gather/__pycache__/publictab.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/gather/__pycache__/publictab.cpython-39.opt-1.pyc new file mode 100644 index 0000000..8c2dd23 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/gather/__pycache__/publictab.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/gather/manualtab.py b/dist/ba_data/python/bastd/ui/gather/manualtab.py index 1d4a677..798b11f 100644 --- a/dist/ba_data/python/bastd/ui/gather/manualtab.py +++ b/dist/ba_data/python/bastd/ui/gather/manualtab.py @@ -15,7 +15,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Optional, Dict, List, Tuple, Type, Union, Callable + from typing import Any, Optional, Union, Callable from bastd.ui.gather import GatherWindow from bastd.ui.confirm import ConfirmWindow @@ -706,7 +706,7 @@ class ManualGatherTab(GatherTab): from_other_thread=True, ) except Exception as exc: - from efro.net import is_udp_network_error + from efro.error import is_udp_network_error if is_udp_network_error(exc): ba.pushcall(ba.Call( _safe_set_text, self._checking_state_text, @@ -850,7 +850,7 @@ class ManualGatherTab(GatherTab): callback=ba.WeakCall( self._on_accessible_response)) - def _on_accessible_response(self, data: Optional[Dict[str, Any]]) -> None: + def _on_accessible_response(self, data: Optional[dict[str, Any]]) -> None: t_addr = self._t_addr t_accessible = self._t_accessible t_accessible_extra = self._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 19fe1c5..03561cb 100644 --- a/dist/ba_data/python/bastd/ui/gather/nearbytab.py +++ b/dist/ba_data/python/bastd/ui/gather/nearbytab.py @@ -12,7 +12,7 @@ import _ba from bastd.ui.gather import GatherTab if TYPE_CHECKING: - from typing import Optional, Dict, Any + from typing import Optional, Any from bastd.ui.gather import GatherWindow @@ -30,7 +30,7 @@ class NetScanner: left_border=10) ba.widget(edit=self._columnwidget, up_widget=tab_button) self._width = width - self._last_selected_host: Optional[Dict[str, Any]] = None + self._last_selected_host: Optional[dict[str, Any]] = None self._update_timer = ba.Timer(1.0, ba.WeakCall(self.update), @@ -44,10 +44,10 @@ class NetScanner: def __del__(self) -> None: _ba.end_host_scanning() - def _on_select(self, host: Dict[str, Any]) -> None: + def _on_select(self, host: dict[str, Any]) -> None: self._last_selected_host = host - def _on_activate(self, host: Dict[str, Any]) -> None: + def _on_activate(self, host: dict[str, Any]) -> None: _ba.connect_to_party(host['address']) def update(self) -> None: diff --git a/dist/ba_data/python/bastd/ui/gather/privatetab.py b/dist/ba_data/python/bastd/ui/gather/privatetab.py index 941be6d..839ba78 100644 --- a/dist/ba_data/python/bastd/ui/gather/privatetab.py +++ b/dist/ba_data/python/bastd/ui/gather/privatetab.py @@ -20,7 +20,7 @@ from bastd.ui.gather import GatherTab from bastd.ui import getcurrency if TYPE_CHECKING: - from typing import Optional, Dict, Any, List, Type + from typing import Optional, Any from bastd.ui.gather import GatherWindow # Print a bit of info about queries, etc. @@ -160,7 +160,7 @@ class PrivateGatherTab(GatherTab): raise RuntimeError(f'Invalid sessiontype {sessiontypestr}') hcfg.session_type = sessiontypestr - sessiontype: Type[ba.Session] + sessiontype: type[ba.Session] if hcfg.session_type == 'ffa': sessiontype = ba.FreeForAllSession elif hcfg.session_type == 'teams': @@ -177,7 +177,7 @@ class PrivateGatherTab(GatherTab): if playlist_name == '__default__' else playlist_name) - playlist: Optional[List[Dict[str, Any]]] = None + playlist: Optional[list[dict[str, Any]]] = None if playlist_name != '__default__': playlist = (cfg.get(f'{pvars.config_name} Playlists', {}).get(playlist_name)) @@ -197,7 +197,7 @@ class PrivateGatherTab(GatherTab): hcfg.tutorial = tutorial if hcfg.session_type == 'teams': - ctn: Optional[List[str]] = cfg.get('Custom Team Names') + ctn: Optional[list[str]] = 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)): @@ -205,7 +205,7 @@ class PrivateGatherTab(GatherTab): else: print(f'Found invalid custom-team-names data: {ctn}') - ctc: Optional[List[List[float]]] = cfg.get('Custom Team Colors') + ctc: Optional[list[list[float]]] = 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) @@ -269,7 +269,7 @@ class PrivateGatherTab(GatherTab): self._last_hosting_state_query_time = now def _hosting_state_idle_response(self, - result: Optional[Dict[str, Any]]) -> None: + result: Optional[dict[str, Any]]) -> None: # This simply passes through to our standard response handler. # The one exception is if we've recently sent an action to the @@ -284,7 +284,7 @@ class PrivateGatherTab(GatherTab): return self._hosting_state_response(result) - def _hosting_state_response(self, result: Optional[Dict[str, + def _hosting_state_response(self, result: Optional[dict[str, Any]]) -> None: # Its possible for this to come back to us after our UI is dead; @@ -344,7 +344,7 @@ class PrivateGatherTab(GatherTab): # Kick off an update to get any needed messages sent/etc. ba.pushcall(self._update) - def _selwidgets(self) -> List[Optional[ba.Widget]]: + def _selwidgets(self) -> list[Optional[ba.Widget]]: """An indexed list of widgets we can use for saving/restoring sel.""" return [ self._host_playlist_button, self._host_copy_button, @@ -844,7 +844,7 @@ class PrivateGatherTab(GatherTab): self._connect_to_party_code(code) - def _connect_response(self, result: Optional[Dict[str, Any]]) -> None: + def _connect_response(self, result: Optional[dict[str, Any]]) -> None: try: self._connect_press_time = None if result is None: diff --git a/dist/ba_data/python/bastd/ui/gather/publictab.py b/dist/ba_data/python/bastd/ui/gather/publictab.py index a219857..b2c1b64 100644 --- a/dist/ba_data/python/bastd/ui/gather/publictab.py +++ b/dist/ba_data/python/bastd/ui/gather/publictab.py @@ -17,7 +17,7 @@ import ba from bastd.ui.gather import GatherTab if TYPE_CHECKING: - from typing import Callable, Any, Optional, Dict, Union, Tuple, List + from typing import Callable, Any, Optional, Union from bastd.ui.gather import GatherWindow # Print a bit of info about pings, queries, etc. @@ -182,7 +182,7 @@ class UIRow: class State: """State saved/restored only while the app is running.""" sub_tab: SubTabType = SubTabType.JOIN - parties: Optional[List[Tuple[str, PartyEntry]]] = None + parties: Optional[list[tuple[str, PartyEntry]]] = None next_entry_index: int = 0 filter_value: str = '' have_server_list_response: bool = False @@ -219,7 +219,7 @@ class AddrFetchThread(threading.Thread): sock.close() ba.pushcall(ba.Call(self._call, val), from_other_thread=True) except Exception as exc: - from efro.net import is_udp_network_error + from efro.error import is_udp_network_error # Ignore expected network errors; log others. if is_udp_network_error(exc): pass @@ -271,7 +271,7 @@ class PingThread(threading.Thread): ping if accessible else None), from_other_thread=True) except Exception as exc: - from efro.net import is_udp_network_error + from efro.error import is_udp_network_error if is_udp_network_error(exc): pass else: @@ -312,26 +312,26 @@ class PublicGatherTab(GatherTab): self._host_max_party_size_plus_button: (Optional[ba.Widget]) = None self._host_status_text: Optional[ba.Widget] = None self._signed_in = False - self._ui_rows: List[UIRow] = [] + self._ui_rows: list[UIRow] = [] self._refresh_ui_row = 0 self._have_user_selected_row = False self._first_valid_server_list_time: Optional[float] = None # Parties indexed by id: - self._parties: Dict[str, PartyEntry] = {} + self._parties: dict[str, PartyEntry] = {} # Parties sorted in display order: - self._parties_sorted: List[Tuple[str, PartyEntry]] = [] + self._parties_sorted: list[tuple[str, PartyEntry]] = [] self._party_lists_dirty = True # Sorted parties with filter applied: - self._parties_displayed: Dict[str, PartyEntry] = {} + self._parties_displayed: dict[str, PartyEntry] = {} self._next_entry_index = 0 self._have_server_list_response = False self._have_valid_server_list = False self._filter_value = '' - self._pending_party_infos: List[Dict[str, Any]] = [] + self._pending_party_infos: list[dict[str, Any]] = [] self._last_sub_scroll_height = 0.0 def on_activate( @@ -715,7 +715,7 @@ class PublicGatherTab(GatherTab): self._do_status_check() def _on_public_party_query_result( - self, result: Optional[Dict[str, Any]]) -> None: + self, result: Optional[dict[str, Any]]) -> None: starttime = time.time() self._have_server_list_response = True @@ -1100,7 +1100,7 @@ class PublicGatherTab(GatherTab): self._local_address = str(val) def _on_public_party_accessible_response( - self, data: Optional[Dict[str, Any]]) -> None: + self, data: Optional[dict[str, Any]]) -> None: # If we've got status text widgets, update them. text = self._host_status_text diff --git a/dist/ba_data/python/bastd/ui/getcurrency.py b/dist/ba_data/python/bastd/ui/getcurrency.py index db19be2..d267406 100644 --- a/dist/ba_data/python/bastd/ui/getcurrency.py +++ b/dist/ba_data/python/bastd/ui/getcurrency.py @@ -10,7 +10,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Optional, Tuple, Union, Dict + from typing import Any, Optional, Union class GetCurrencyWindow(ba.Window): @@ -38,7 +38,7 @@ class GetCurrencyWindow(ba.Window): self._ad_time_text = None # If they provided an origin-widget, scale up from that. - scale_origin: Optional[Tuple[float, float]] + scale_origin: Optional[tuple[float, float]] if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() @@ -102,8 +102,8 @@ class GetCurrencyWindow(ba.Window): self._ad_button = None def _add_button(item: str, - position: Tuple[float, float], - size: Tuple[float, float], + position: tuple[float, float], + size: tuple[float, float], label: ba.Lstr, price: str = None, tex_name: str = None, @@ -333,10 +333,10 @@ class GetCurrencyWindow(ba.Window): txt1 = (ba.Lstr( resource=self._r + - '.youHaveText').evaluate().split('${COUNT}')[0].strip()) + '.youHaveText').evaluate().partition('${COUNT}')[0].strip()) txt2 = (ba.Lstr( resource=self._r + - '.youHaveText').evaluate().split('${COUNT}')[-1].strip()) + '.youHaveText').evaluate().rpartition('${COUNT}')[0].strip()) ba.textwidget(parent=self._root_widget, text=txt1, @@ -528,7 +528,7 @@ class GetCurrencyWindow(ba.Window): item)) def _purchase_check_result(self, item: str, - result: Optional[Dict[str, Any]]) -> None: + result: Optional[dict[str, Any]]) -> None: if result is None: ba.playsound(ba.getsound('error')) ba.screenmessage( diff --git a/dist/ba_data/python/bastd/ui/helpui.py b/dist/ba_data/python/bastd/ui/helpui.py index 2fda7f8..77594b0 100644 --- a/dist/ba_data/python/bastd/ui/helpui.py +++ b/dist/ba_data/python/bastd/ui/helpui.py @@ -10,7 +10,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Optional, Tuple + from typing import Optional class HelpWindow(ba.Window): @@ -25,7 +25,7 @@ class HelpWindow(ba.Window): ba.set_analytics_screen('Help Window') # If they provided an origin-widget, scale up from that. - scale_origin: Optional[Tuple[float, float]] + scale_origin: Optional[tuple[float, float]] if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() diff --git a/dist/ba_data/python/bastd/ui/iconpicker.py b/dist/ba_data/python/bastd/ui/iconpicker.py index 1d1820f..6c6f07d 100644 --- a/dist/ba_data/python/bastd/ui/iconpicker.py +++ b/dist/ba_data/python/bastd/ui/iconpicker.py @@ -12,7 +12,7 @@ import ba from bastd.ui import popup if TYPE_CHECKING: - from typing import Any, Tuple, Sequence + from typing import Any, Sequence class IconPicker(popup.PopupWindow): @@ -20,10 +20,10 @@ class IconPicker(popup.PopupWindow): def __init__(self, parent: ba.Widget, - position: Tuple[float, float] = (0.0, 0.0), + position: tuple[float, float] = (0.0, 0.0), delegate: Any = None, scale: float = None, - offset: Tuple[float, float] = (0.0, 0.0), + 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): diff --git a/dist/ba_data/python/bastd/ui/league/__pycache__/__init__.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/league/__pycache__/__init__.cpython-39.opt-1.pyc new file mode 100644 index 0000000..ebcba24 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/league/__pycache__/__init__.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/league/__pycache__/rankbutton.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/league/__pycache__/rankbutton.cpython-39.opt-1.pyc new file mode 100644 index 0000000..dfe3bed Binary files /dev/null and b/dist/ba_data/python/bastd/ui/league/__pycache__/rankbutton.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/league/__pycache__/rankwindow.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/ui/league/__pycache__/rankwindow.cpython-38.opt-1.pyc index 020fc9e..a9cb04f 100644 Binary files a/dist/ba_data/python/bastd/ui/league/__pycache__/rankwindow.cpython-38.opt-1.pyc and b/dist/ba_data/python/bastd/ui/league/__pycache__/rankwindow.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/league/__pycache__/rankwindow.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/league/__pycache__/rankwindow.cpython-39.opt-1.pyc new file mode 100644 index 0000000..fc97c1f Binary files /dev/null and b/dist/ba_data/python/bastd/ui/league/__pycache__/rankwindow.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/league/rankbutton.py b/dist/ba_data/python/bastd/ui/league/rankbutton.py index f299f5f..d7ab53e 100644 --- a/dist/ba_data/python/bastd/ui/league/rankbutton.py +++ b/dist/ba_data/python/bastd/ui/league/rankbutton.py @@ -10,7 +10,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Tuple, Optional, Callable, Dict, Union + from typing import Any, Optional, Callable, Union class LeagueRankButton: @@ -18,13 +18,13 @@ class LeagueRankButton: def __init__(self, parent: ba.Widget, - position: Tuple[float, float], - size: Tuple[float, float], + position: tuple[float, float], + size: tuple[float, float], scale: float, on_activate_call: Callable[[], Any] = None, transition_delay: float = None, - color: Tuple[float, float, float] = None, - textcolor: Tuple[float, float, float] = None, + color: tuple[float, float, float] = None, + textcolor: tuple[float, float, float] = None, smooth_update_delay: float = None): if on_activate_call is None: on_activate_call = ba.WeakCall(self._default_on_activate_call) @@ -42,7 +42,7 @@ class LeagueRankButton: self._textcolor = textcolor self._header_color = (0.8, 0.8, 2.0) self._parent = parent - self._position: Tuple[float, float] = (0.0, 0.0) + self._position: tuple[float, float] = (0.0, 0.0) self._button = ba.buttonwidget(parent=parent, size=size, @@ -211,7 +211,7 @@ class LeagueRankButton: ba.print_exception('Error doing smooth update.') self._smooth_update_timer = None - def _update_for_league_rank_data(self, data: Optional[Dict[str, + def _update_for_league_rank_data(self, data: Optional[dict[str, Any]]) -> None: # pylint: disable=too-many-branches # pylint: disable=too-many-statements @@ -325,7 +325,7 @@ class LeagueRankButton: ba.textwidget(edit=self._value_text, text=status_text) def _on_power_ranking_query_response( - self, data: Optional[Dict[str, Any]]) -> None: + self, data: Optional[dict[str, Any]]) -> None: self._doing_power_ranking_query = False ba.app.accounts.cache_league_rank_data(data) self._update_for_league_rank_data(data) @@ -357,7 +357,7 @@ class LeagueRankButton: from bastd.ui.league.rankwindow import LeagueRankWindow LeagueRankWindow(modal=True, origin_widget=self._button) - def set_position(self, position: Tuple[float, float]) -> None: + def set_position(self, position: tuple[float, float]) -> None: """Set the button's position.""" self._position = position if not self._button: diff --git a/dist/ba_data/python/bastd/ui/league/rankwindow.py b/dist/ba_data/python/bastd/ui/league/rankwindow.py index 0ee0c9d..d1e31c4 100644 --- a/dist/ba_data/python/bastd/ui/league/rankwindow.py +++ b/dist/ba_data/python/bastd/ui/league/rankwindow.py @@ -12,7 +12,7 @@ import ba from bastd.ui import popup as popup_ui if TYPE_CHECKING: - from typing import Any, Optional, Tuple, List, Dict, Union + from typing import Any, Optional, Union class LeagueRankWindow(ba.Window): @@ -24,11 +24,11 @@ class LeagueRankWindow(ba.Window): origin_widget: ba.Widget = None): ba.set_analytics_screen('League Rank Window') - self._league_rank_data: Optional[Dict[str, Any]] = None + self._league_rank_data: Optional[dict[str, Any]] = None self._modal = modal # If they provided an origin-widget, scale up from that. - scale_origin: Optional[Tuple[float, float]] + scale_origin: Optional[tuple[float, float]] if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() @@ -111,7 +111,7 @@ class LeagueRankWindow(ba.Window): self._subcontainer: Optional[ba.Widget] = None self._subcontainerwidth = 800 self._subcontainerheight = 483 - self._power_ranking_score_widgets: List[ba.Widget] = [] + self._power_ranking_score_widgets: list[ba.Widget] = [] self._season_popup_menu: Optional[popup_ui.PopupMenu] = None self._requested_season: Optional[str] = None @@ -192,7 +192,7 @@ class LeagueRankWindow(ba.Window): ba.playsound(ba.getsound('error')) def _on_power_ranking_query_response( - self, data: Optional[Dict[str, Any]]) -> None: + self, data: Optional[dict[str, Any]]) -> 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: @@ -587,7 +587,7 @@ class LeagueRankWindow(ba.Window): '/highscores?list=powerRankings&v=2' + league_str + season_str + '&player=' + our_login_id) - def _update_for_league_rank_data(self, data: Optional[Dict[str, + def _update_for_league_rank_data(self, data: Optional[dict[str, Any]]) -> None: # pylint: disable=too-many-statements # pylint: disable=too-many-branches @@ -784,12 +784,14 @@ class LeagueRankWindow(ba.Window): 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'])) have_pro = False if data is None else data['p'] pro_mult = 1.0 + float( _ba.get_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 ' + diff --git a/dist/ba_data/python/bastd/ui/mainmenu.py b/dist/ba_data/python/bastd/ui/mainmenu.py index c82c7eb..420b6cf 100644 --- a/dist/ba_data/python/bastd/ui/mainmenu.py +++ b/dist/ba_data/python/bastd/ui/mainmenu.py @@ -11,7 +11,7 @@ import ba import _ba if TYPE_CHECKING: - from typing import Any, Callable, List, Dict, Tuple, Optional, Union + from typing import Any, Callable, Optional, Union class MainMenuWindow(ba.Window): @@ -190,7 +190,7 @@ class MainMenuWindow(ba.Window): input_device.is_connected_to_remote_player() if input_device else False) - positions: List[Tuple[float, float, float]] = [] + positions: list[tuple[float, float, float]] = [] self._p_index = 0 if self._in_game: @@ -412,8 +412,8 @@ class MainMenuWindow(ba.Window): 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 @@ -675,12 +675,12 @@ class MainMenuWindow(ba.Window): 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 - custom_menu_entries: List[Dict[str, Any]] = [] + custom_menu_entries: list[dict[str, Any]] = [] session = _ba.get_foreground_host_session() if session is not None: try: diff --git a/dist/ba_data/python/bastd/ui/onscreenkeyboard.py b/dist/ba_data/python/bastd/ui/onscreenkeyboard.py index d5ad875..4a446a3 100644 --- a/dist/ba_data/python/bastd/ui/onscreenkeyboard.py +++ b/dist/ba_data/python/bastd/ui/onscreenkeyboard.py @@ -12,7 +12,7 @@ from ba import charstr from ba import SpecialChar as SpCh if TYPE_CHECKING: - from typing import List, Tuple, Optional + from typing import Optional class OnScreenKeyboardWindow(ba.Window): @@ -78,14 +78,14 @@ class OnScreenKeyboardWindow(ba.Window): self._double_press_shift = False self._num_mode_button: Optional[ba.Widget] = None self._emoji_button: Optional[ba.Widget] = None - self._char_keys: List[ba.Widget] = [] + self._char_keys: list[ba.Widget] = [] self._keyboard_index = 0 self._last_space_press = 0.0 self._double_space_interval = 0.3 self._keyboard: ba.Keyboard - self._chars: List[str] - self._modes: List[str] + self._chars: list[str] + self._modes: list[str] self._mode: str self._mode_index: int self._load_keyboard() @@ -116,7 +116,7 @@ class OnScreenKeyboardWindow(ba.Window): # dummy data just used for row/column lengths... we don't actually # set things until refresh - chars: List[Tuple[str, ...]] = self._keyboard.chars + chars: list[tuple[str, ...]] = self._keyboard.chars for row_num, row in enumerate(chars): h = row_starts[row_num] @@ -244,7 +244,7 @@ class OnScreenKeyboardWindow(ba.Window): return kbclass() def _refresh(self) -> None: - chars: Optional[List[str]] = None + chars: Optional[list[str]] = None if self._mode in ['normal', 'caps']: chars = list(self._chars) if self._mode == 'caps': diff --git a/dist/ba_data/python/bastd/ui/party.py b/dist/ba_data/python/bastd/ui/party.py index 041fc4f..307b570 100644 --- a/dist/ba_data/python/bastd/ui/party.py +++ b/dist/ba_data/python/bastd/ui/party.py @@ -13,7 +13,7 @@ import ba from bastd.ui import popup if TYPE_CHECKING: - from typing import List, Sequence, Optional, Dict, Any + from typing import Sequence, Optional, Any class PartyWindow(ba.Window): @@ -113,7 +113,7 @@ class PartyWindow(ba.Window): h_align='center', v_align='center', text=ba.Lstr(resource='chatMutedText')) - self._chat_texts: List[ba.Widget] = [] + self._chat_texts: list[ba.Widget] = [] # add all existing messages if chat is not muted if not ba.app.config.resolve('Chat Muted'): @@ -153,8 +153,8 @@ class PartyWindow(ba.Window): 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: Optional[List[Dict[str, Any]]] = None + self._name_widgets: list[ba.Widget] = [] + self._roster: Optional[list[dict[str, Any]]] = None self._update_timer = ba.Timer(1.0, ba.WeakCall(self._update), repeat=True, diff --git a/dist/ba_data/python/bastd/ui/partyqueue.py b/dist/ba_data/python/bastd/ui/partyqueue.py index fd8df48..f1b5a23 100644 --- a/dist/ba_data/python/bastd/ui/partyqueue.py +++ b/dist/ba_data/python/bastd/ui/partyqueue.py @@ -12,7 +12,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Optional, Sequence, List, Dict + from typing import Any, Optional, Sequence class PartyQueueWindow(ba.Window): @@ -181,8 +181,8 @@ class PartyQueueWindow(ba.Window): self._boost_price: Optional[ba.Widget] = None self._boost_label: Optional[ba.Widget] = None self._field_shown = False - self._dudes: List[PartyQueueWindow.Dude] = [] - self._dudes_by_id: Dict[int, PartyQueueWindow.Dude] = {} + self._dudes: list[PartyQueueWindow.Dude] = [] + self._dudes_by_id: dict[int, PartyQueueWindow.Dude] = {} self._line_left = 40.0 self._line_width = self._width - 190 self._line_bottom = self._height * 0.4 @@ -292,7 +292,7 @@ class PartyQueueWindow(ba.Window): """Close the ui.""" ba.containerwidget(edit=self._root_widget, transition='out_scale') - def _update_field(self, response: Dict[str, Any]) -> None: + def _update_field(self, response: dict[str, Any]) -> None: if self._angry_computer_image is None: self._angry_computer_image = ba.imagewidget( parent=self._root_widget, @@ -356,7 +356,7 @@ class PartyQueueWindow(ba.Window): self._dudes = [] self._dudes_by_id = {} - def on_update_response(self, response: Optional[Dict[str, Any]]) -> None: + def on_update_response(self, response: Optional[dict[str, Any]]) -> None: """We've received a response from an update to the server.""" # pylint: disable=too-many-branches if not self._root_widget: diff --git a/dist/ba_data/python/bastd/ui/play.py b/dist/ba_data/python/bastd/ui/play.py index 620b7a0..adfeb3e 100644 --- a/dist/ba_data/python/bastd/ui/play.py +++ b/dist/ba_data/python/bastd/ui/play.py @@ -10,7 +10,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Optional, Tuple + from typing import Optional class PlayWindow(ba.Window): @@ -37,7 +37,7 @@ class PlayWindow(ba.Window): height = 550 button_width = 400 - scale_origin: Optional[Tuple[float, float]] + scale_origin: Optional[tuple[float, float]] if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() @@ -476,8 +476,8 @@ class PlayWindow(ba.Window): 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: + 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], diff --git a/dist/ba_data/python/bastd/ui/playlist/__init__.py b/dist/ba_data/python/bastd/ui/playlist/__init__.py index 483008c..5aeadf6 100644 --- a/dist/ba_data/python/bastd/ui/playlist/__init__.py +++ b/dist/ba_data/python/bastd/ui/playlist/__init__.py @@ -9,17 +9,17 @@ from typing import TYPE_CHECKING import ba if TYPE_CHECKING: - from typing import Type + pass # FIXME: Could change this to be a classmethod of session types? class PlaylistTypeVars: """Defines values for a playlist type (config names to use, etc).""" - def __init__(self, sessiontype: Type[ba.Session]): + def __init__(self, sessiontype: type[ba.Session]): from ba.internal import (get_default_teams_playlist, get_default_free_for_all_playlist) - self.sessiontype: Type[ba.Session] + self.sessiontype: type[ba.Session] if issubclass(sessiontype, ba.DualTeamSession): play_mode_name = ba.Lstr(resource='playModes.teamsText', diff --git a/dist/ba_data/python/bastd/ui/playlist/__pycache__/__init__.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/playlist/__pycache__/__init__.cpython-39.opt-1.pyc new file mode 100644 index 0000000..fcb404c Binary files /dev/null and b/dist/ba_data/python/bastd/ui/playlist/__pycache__/__init__.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/playlist/__pycache__/addgame.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/playlist/__pycache__/addgame.cpython-39.opt-1.pyc new file mode 100644 index 0000000..ba0c99d Binary files /dev/null and b/dist/ba_data/python/bastd/ui/playlist/__pycache__/addgame.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/playlist/__pycache__/browser.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/playlist/__pycache__/browser.cpython-39.opt-1.pyc new file mode 100644 index 0000000..9a3dfd7 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/playlist/__pycache__/browser.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/playlist/__pycache__/customizebrowser.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/playlist/__pycache__/customizebrowser.cpython-39.opt-1.pyc new file mode 100644 index 0000000..128f257 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/playlist/__pycache__/customizebrowser.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/playlist/__pycache__/edit.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/playlist/__pycache__/edit.cpython-39.opt-1.pyc new file mode 100644 index 0000000..152ebce Binary files /dev/null and b/dist/ba_data/python/bastd/ui/playlist/__pycache__/edit.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/playlist/__pycache__/editcontroller.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/playlist/__pycache__/editcontroller.cpython-39.opt-1.pyc new file mode 100644 index 0000000..3024741 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/playlist/__pycache__/editcontroller.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/playlist/__pycache__/editgame.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/playlist/__pycache__/editgame.cpython-39.opt-1.pyc new file mode 100644 index 0000000..ed04296 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/playlist/__pycache__/editgame.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/playlist/__pycache__/mapselect.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/playlist/__pycache__/mapselect.cpython-39.opt-1.pyc new file mode 100644 index 0000000..97ab137 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/playlist/__pycache__/mapselect.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/playlist/__pycache__/share.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/playlist/__pycache__/share.cpython-39.opt-1.pyc new file mode 100644 index 0000000..ccb0371 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/playlist/__pycache__/share.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/playlist/addgame.py b/dist/ba_data/python/bastd/ui/playlist/addgame.py index af51c57..f12f26b 100644 --- a/dist/ba_data/python/bastd/ui/playlist/addgame.py +++ b/dist/ba_data/python/bastd/ui/playlist/addgame.py @@ -10,7 +10,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Type, Optional + from typing import Optional from bastd.ui.playlist.editcontroller import PlaylistEditController @@ -112,7 +112,7 @@ class PlaylistAddGameWindow(ba.Window): ba.containerwidget(edit=self._root_widget, cancel_button=self._back_button, start_button=select_button) - self._selected_game_type: Optional[Type[ba.GameActivity]] = None + self._selected_game_type: Optional[type[ba.GameActivity]] = None ba.containerwidget(edit=self._root_widget, selected_child=self._scrollwidget) @@ -193,7 +193,7 @@ class PlaylistAddGameWindow(ba.Window): assert self._selected_game_type is not None self._editcontroller.add_game_type_selected(self._selected_game_type) - def _set_selected_game_type(self, gametype: Type[ba.GameActivity]) -> None: + 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()) diff --git a/dist/ba_data/python/bastd/ui/playlist/browser.py b/dist/ba_data/python/bastd/ui/playlist/browser.py index 0a3c7ef..7646b3f 100644 --- a/dist/ba_data/python/bastd/ui/playlist/browser.py +++ b/dist/ba_data/python/bastd/ui/playlist/browser.py @@ -12,14 +12,14 @@ import _ba import ba if TYPE_CHECKING: - from typing import Type, Optional, Tuple, Union + from typing import Optional, Union class PlaylistBrowserWindow(ba.Window): """Window for starting teams games.""" def __init__(self, - sessiontype: Type[ba.Session], + sessiontype: type[ba.Session], transition: Optional[str] = 'in_right', origin_widget: ba.Widget = None): # pylint: disable=too-many-statements @@ -27,7 +27,7 @@ class PlaylistBrowserWindow(ba.Window): from bastd.ui.playlist import PlaylistTypeVars # If they provided an origin-widget, scale up from that. - scale_origin: Optional[Tuple[float, float]] + scale_origin: Optional[tuple[float, float]] if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() @@ -432,7 +432,7 @@ class PlaylistBrowserWindow(ba.Window): mark_unowned=True) for entry in playlist: mapname = entry['settings']['map'] - maptype: Optional[Type[ba.Map]] + maptype: Optional[type[ba.Map]] try: maptype = get_map_class(mapname) except ba.NotFoundError: diff --git a/dist/ba_data/python/bastd/ui/playlist/customizebrowser.py b/dist/ba_data/python/bastd/ui/playlist/customizebrowser.py index 4ee5b29..917e65e 100644 --- a/dist/ba_data/python/bastd/ui/playlist/customizebrowser.py +++ b/dist/ba_data/python/bastd/ui/playlist/customizebrowser.py @@ -12,14 +12,14 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Type, Optional, Tuple, List, Dict + from typing import Any, Optional class PlaylistCustomizeBrowserWindow(ba.Window): """Window for viewing a playlist.""" def __init__(self, - sessiontype: Type[ba.Session], + sessiontype: type[ba.Session], transition: str = 'in_right', select_playlist: str = None, origin_widget: ba.Widget = None): @@ -28,7 +28,7 @@ class PlaylistCustomizeBrowserWindow(ba.Window): # pylint: disable=too-many-statements # pylint: disable=cyclic-import from bastd.ui import playlist - scale_origin: Optional[Tuple[float, float]] + scale_origin: Optional[tuple[float, float]] if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() @@ -86,7 +86,7 @@ class PlaylistCustomizeBrowserWindow(ba.Window): h = 41 + x_inset b_color = (0.6, 0.53, 0.63) b_textcolor = (0.75, 0.7, 0.8) - self._lock_images: List[ba.Widget] = [] + self._lock_images: list[ba.Widget] = [] lock_tex = ba.gettexture('lock') scl = (1.1 if uiscale is ba.UIScale.SMALL else @@ -236,7 +236,7 @@ class PlaylistCustomizeBrowserWindow(ba.Window): self._selected_playlist_name: Optional[str] = None self._selected_playlist_index: Optional[int] = None - self._playlist_widgets: List[ba.Widget] = [] + self._playlist_widgets: list[ba.Widget] = [] self._refresh(select_playlist=select_playlist) @@ -539,7 +539,7 @@ class PlaylistCustomizeBrowserWindow(ba.Window): return if self._selected_playlist_name is None: return - plst: Optional[List[Dict[str, Any]]] + plst: Optional[list[dict[str, Any]]] if self._selected_playlist_name == '__default__': plst = self._pvars.get_default_list_call() else: diff --git a/dist/ba_data/python/bastd/ui/playlist/edit.py b/dist/ba_data/python/bastd/ui/playlist/edit.py index cb5388a..2b9e4c4 100644 --- a/dist/ba_data/python/bastd/ui/playlist/edit.py +++ b/dist/ba_data/python/bastd/ui/playlist/edit.py @@ -10,7 +10,7 @@ import ba import _ba if TYPE_CHECKING: - from typing import Optional, List + from typing import Optional from bastd.ui.playlist.editcontroller import PlaylistEditController @@ -106,7 +106,7 @@ class PlaylistEditWindow(ba.Window): 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] = [] + self._list_widgets: list[ba.Widget] = [] h = 40 + x_inset v = self._height - 172.0 diff --git a/dist/ba_data/python/bastd/ui/playlist/editcontroller.py b/dist/ba_data/python/bastd/ui/playlist/editcontroller.py index 5c37c53..bf3e832 100644 --- a/dist/ba_data/python/bastd/ui/playlist/editcontroller.py +++ b/dist/ba_data/python/bastd/ui/playlist/editcontroller.py @@ -10,17 +10,17 @@ from typing import TYPE_CHECKING import ba if TYPE_CHECKING: - from typing import Any, Type, List, Dict, Optional + from typing import Any, Optional class PlaylistEditController: """Coordinates various UIs involved in playlist editing.""" def __init__(self, - sessiontype: Type[ba.Session], + sessiontype: type[ba.Session], existing_playlist_name: str = None, transition: str = 'in_right', - playlist: List[Dict[str, Any]] = None, + playlist: list[dict[str, Any]] = None, playlist_name: str = None): from ba.internal import preload_map_preview_media, filter_playlist from bastd.ui.playlist import PlaylistTypeVars @@ -34,7 +34,7 @@ class PlaylistEditController: self._sessiontype = sessiontype self._editing_game = False - self._editing_game_type: Optional[Type[ba.GameActivity]] = None + self._editing_game_type: Optional[type[ba.GameActivity]] = None self._pvars = PlaylistTypeVars(sessiontype) self._existing_playlist_name = existing_playlist_name self._config_name_full = self._pvars.config_name + ' Playlists' @@ -106,15 +106,15 @@ class PlaylistEditController: """(internal)""" self._name = name - def get_playlist(self) -> List[Dict[str, Any]]: + def get_playlist(self) -> list[dict[str, Any]]: """Return the current state of the edited playlist.""" return copy.deepcopy(self._playlist) - def set_playlist(self, playlist: List[Dict[str, Any]]) -> None: + def set_playlist(self, playlist: list[dict[str, Any]]) -> None: """Set the playlist contents.""" self._playlist = copy.deepcopy(playlist) - def get_session_type(self) -> Type[ba.Session]: + def get_session_type(self) -> type[ba.Session]: """Return the ba.Session type for this edit-session.""" return self._sessiontype @@ -155,19 +155,19 @@ class PlaylistEditController: PlaylistEditWindow(editcontroller=self, transition='in_left').get_root_widget()) - def _show_edit_ui(self, gametype: Type[ba.GameActivity], - settings: Optional[Dict[str, Any]]) -> None: + def _show_edit_ui(self, gametype: type[ba.GameActivity], + settings: Optional[dict[str, Any]]) -> 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) - def add_game_type_selected(self, gametype: Type[ba.GameActivity]) -> None: + def add_game_type_selected(self, gametype: type[ba.GameActivity]) -> None: """(internal)""" self._show_edit_ui(gametype=gametype, settings=None) - def _edit_game_done(self, config: Optional[Dict[str, Any]]) -> None: + def _edit_game_done(self, config: Optional[dict[str, Any]]) -> None: from bastd.ui.playlist.edit import PlaylistEditWindow from bastd.ui.playlist.addgame import PlaylistAddGameWindow from ba.internal import get_type_name diff --git a/dist/ba_data/python/bastd/ui/playlist/editgame.py b/dist/ba_data/python/bastd/ui/playlist/editgame.py index 8533cc1..9844108 100644 --- a/dist/ba_data/python/bastd/ui/playlist/editgame.py +++ b/dist/ba_data/python/bastd/ui/playlist/editgame.py @@ -12,20 +12,20 @@ import _ba import ba if TYPE_CHECKING: - from typing import Type, Any, Dict, Callable, Optional, Union, List + from typing import Any, Callable, Optional, Union class PlaylistEditGameWindow(ba.Window): """Window for editing a game config.""" def __init__(self, - gametype: Type[ba.GameActivity], - sessiontype: Type[ba.Session], - config: Optional[Dict[str, Any]], - completion_call: Callable[[Optional[Dict[str, Any]]], Any], + gametype: type[ba.GameActivity], + sessiontype: type[ba.Session], + config: Optional[dict[str, Any]], + completion_call: Callable[[Optional[dict[str, Any]]], Any], default_selection: str = None, transition: str = 'in_right', - edit_info: Dict[str, Any] = None): + edit_info: dict[str, Any] = None): # pylint: disable=too-many-branches # pylint: disable=too-many-statements # pylint: disable=too-many-locals @@ -86,7 +86,7 @@ class PlaylistEditGameWindow(ba.Window): else: self._settings = {} - self._choice_selections: Dict[str, int] = {} + self._choice_selections: dict[str, int] = {} uiscale = ba.app.ui.uiscale width = 720 if uiscale is ba.UIScale.SMALL else 620 @@ -176,7 +176,7 @@ class PlaylistEditGameWindow(ba.Window): # Keep track of all the selectable widgets we make so we can wire # them up conveniently. - widget_column: List[List[ba.Widget]] = [] + widget_column: list[list[ba.Widget]] = [] # Map select button. ba.textwidget(parent=self._subcontainer, @@ -392,7 +392,7 @@ class PlaylistEditGameWindow(ba.Window): # Ok now wire up the column. try: # pylint: disable=unsubscriptable-object - prev_widgets: Optional[List[ba.Widget]] = None + prev_widgets: Optional[list[ba.Widget]] = None for cwdg in widget_column: if prev_widgets is not None: # Wire our rightmost to their rightmost. @@ -458,7 +458,7 @@ class PlaylistEditGameWindow(ba.Window): resource='offText')) self._settings[setting_name] = value - def _getconfig(self) -> Dict[str, Any]: + def _getconfig(self) -> dict[str, Any]: settings = copy.deepcopy(self._settings) settings['map'] = self._map return {'settings': settings} @@ -468,7 +468,7 @@ class PlaylistEditGameWindow(ba.Window): def _inc(self, ctrl: ba.Widget, min_val: Union[int, float], max_val: Union[int, float], increment: Union[int, float], - setting_type: Type, setting_name: str) -> None: + 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 2381f6c..fc79ce7 100644 --- a/dist/ba_data/python/bastd/ui/playlist/mapselect.py +++ b/dist/ba_data/python/bastd/ui/playlist/mapselect.py @@ -11,18 +11,18 @@ import _ba import ba if TYPE_CHECKING: - from typing import Type, Any, Callable, Dict, List, Tuple, Optional + from typing import Any, Callable, Optional 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[[Optional[Dict[str, Any]]], Any], + gametype: type[ba.GameActivity], + sessiontype: type[ba.Session], + config: dict[str, Any], + edit_info: dict[str, Any], + completion_call: Callable[[Optional[dict[str, Any]]], Any], transition: str = 'in_right'): from ba.internal import get_filtered_map_name self._gametype = gametype @@ -30,7 +30,7 @@ class PlaylistMapSelectWindow(ba.Window): self._config = config self._completion_call = completion_call self._edit_info = edit_info - self._maps: List[Tuple[str, ba.Texture]] = [] + self._maps: list[tuple[str, ba.Texture]] = [] try: self._previous_map = get_filtered_map_name( config['settings']['map']) diff --git a/dist/ba_data/python/bastd/ui/playlist/share.py b/dist/ba_data/python/bastd/ui/playlist/share.py index 0cd1ea4..8355c39 100644 --- a/dist/ba_data/python/bastd/ui/playlist/share.py +++ b/dist/ba_data/python/bastd/ui/playlist/share.py @@ -12,7 +12,7 @@ import ba from bastd.ui import promocode if TYPE_CHECKING: - from typing import Any, Callable, Dict, Optional, Tuple + from typing import Any, Callable, Optional class SharePlaylistImportWindow(promocode.PromoCodeWindow): @@ -26,7 +26,7 @@ class SharePlaylistImportWindow(promocode.PromoCodeWindow): origin_widget=origin_widget) self._on_success_callback = on_success_callback - def _on_import_response(self, response: Optional[Dict[str, Any]]) -> None: + def _on_import_response(self, response: Optional[dict[str, Any]]) -> None: if response is None: ba.screenmessage(ba.Lstr(resource='errorText'), color=(1, 0, 0)) ba.playsound(ba.getsound('error')) @@ -67,7 +67,7 @@ class SharePlaylistResultsWindow(ba.Window): def __init__(self, name: str, data: str, - origin: Tuple[float, float] = (0.0, 0.0)): + origin: tuple[float, float] = (0.0, 0.0)): del origin # unused arg self._width = 450 self._height = 300 diff --git a/dist/ba_data/python/bastd/ui/playoptions.py b/dist/ba_data/python/bastd/ui/playoptions.py index af6725d..e4b16d5 100644 --- a/dist/ba_data/python/bastd/ui/playoptions.py +++ b/dist/ba_data/python/bastd/ui/playoptions.py @@ -11,16 +11,16 @@ import ba from bastd.ui import popup if TYPE_CHECKING: - from typing import Any, Type, Tuple, Optional, Union + from typing import Any, Optional, Union class PlayOptionsWindow(popup.PopupWindow): """A popup window for configuring play options.""" def __init__(self, - sessiontype: Type[ba.Session], + sessiontype: type[ba.Session], playlist: str, - scale_origin: Tuple[float, float], + scale_origin: tuple[float, float], delegate: Any = None): # FIXME: Tidy this up. # pylint: disable=too-many-branches @@ -90,7 +90,7 @@ class PlayOptionsWindow(popup.PopupWindow): game_count = len(plst) for entry in plst: mapname = entry['settings']['map'] - maptype: Optional[Type[ba.Map]] + maptype: Optional[type[ba.Map]] try: maptype = get_map_class(mapname) except ba.NotFoundError: diff --git a/dist/ba_data/python/bastd/ui/popup.py b/dist/ba_data/python/bastd/ui/popup.py index 500c379..d89374b 100644 --- a/dist/ba_data/python/bastd/ui/popup.py +++ b/dist/ba_data/python/bastd/ui/popup.py @@ -11,20 +11,20 @@ import _ba import ba if TYPE_CHECKING: - from typing import Tuple, Any, Sequence, Callable, Optional, List, Union + from typing import Any, Sequence, Callable, Optional, Union class PopupWindow: """A transient window that positions and scales itself for visibility.""" def __init__(self, - position: Tuple[float, float], - size: Tuple[float, float], + 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, + 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, toolbar_visibility: str = 'menu_minimal_no_back'): # pylint: disable=too-many-locals if focus_size is None: @@ -101,7 +101,7 @@ class PopupMenuWindow(PopupWindow): """A menu built using popup-window functionality.""" def __init__(self, - position: Tuple[float, float], + position: tuple[float, float], choices: Sequence[str], current_choice: str, delegate: Any = None, @@ -121,7 +121,7 @@ class PopupMenuWindow(PopupWindow): # FIXME: For the moment we base our width on these strings so # we need to flatten them. - choices_display_fin: List[str] = [] + choices_display_fin: list[str] = [] for choice_display in choices_display: choices_display_fin.append(choice_display.evaluate()) @@ -264,7 +264,7 @@ class PopupMenu: def __init__(self, parent: ba.Widget, - position: Tuple[float, float], + position: tuple[float, float], choices: Sequence[str], current_choice: str = None, on_value_change_call: Callable[[str], Any] = None, @@ -275,7 +275,7 @@ class PopupMenu: scale: float = None, choices_disabled: Sequence[str] = None, choices_display: Sequence[ba.Lstr] = None, - button_size: Tuple[float, float] = (160.0, 50.0), + button_size: tuple[float, float] = (160.0, 50.0), autoselect: bool = True): # pylint: disable=too-many-locals if choices_disabled is None: diff --git a/dist/ba_data/python/bastd/ui/profile/__pycache__/__init__.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/profile/__pycache__/__init__.cpython-39.opt-1.pyc new file mode 100644 index 0000000..de6c0cc Binary files /dev/null and b/dist/ba_data/python/bastd/ui/profile/__pycache__/__init__.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/profile/__pycache__/__init__.cpython-39.pyc b/dist/ba_data/python/bastd/ui/profile/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..28c9c4b Binary files /dev/null and b/dist/ba_data/python/bastd/ui/profile/__pycache__/__init__.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/ui/profile/__pycache__/browser.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/profile/__pycache__/browser.cpython-39.opt-1.pyc new file mode 100644 index 0000000..8d2b717 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/profile/__pycache__/browser.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/profile/__pycache__/browser.cpython-39.pyc b/dist/ba_data/python/bastd/ui/profile/__pycache__/browser.cpython-39.pyc new file mode 100644 index 0000000..4d783ca Binary files /dev/null and b/dist/ba_data/python/bastd/ui/profile/__pycache__/browser.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/ui/profile/__pycache__/edit.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/profile/__pycache__/edit.cpython-39.opt-1.pyc new file mode 100644 index 0000000..487fcf2 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/profile/__pycache__/edit.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/profile/__pycache__/upgrade.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/profile/__pycache__/upgrade.cpython-39.opt-1.pyc new file mode 100644 index 0000000..5493e68 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/profile/__pycache__/upgrade.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/profile/browser.py b/dist/ba_data/python/bastd/ui/profile/browser.py index e82fd29..8d0ac7e 100644 --- a/dist/ba_data/python/bastd/ui/profile/browser.py +++ b/dist/ba_data/python/bastd/ui/profile/browser.py @@ -10,7 +10,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Optional, Tuple, List, Dict + from typing import Any, Optional class ProfileBrowserWindow(ba.Window): @@ -39,7 +39,7 @@ class ProfileBrowserWindow(ba.Window): ba.app.pause() # If they provided an origin-widget, scale up from that. - scale_origin: Optional[Tuple[float, float]] + scale_origin: Optional[tuple[float, float]] if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() @@ -162,9 +162,9 @@ class ProfileBrowserWindow(ba.Window): border=2, margin=0) v -= 255 - self._profiles: Optional[Dict[str, Dict[str, Any]]] = None + self._profiles: Optional[dict[str, dict[str, Any]]] = None self._selected_profile = selected_profile - self._profile_widgets: List[ba.Widget] = [] + self._profile_widgets: list[ba.Widget] = [] self._refresh() self._restore_state() diff --git a/dist/ba_data/python/bastd/ui/profile/edit.py b/dist/ba_data/python/bastd/ui/profile/edit.py index c63cd16..d7df39a 100644 --- a/dist/ba_data/python/bastd/ui/profile/edit.py +++ b/dist/ba_data/python/bastd/ui/profile/edit.py @@ -11,7 +11,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Tuple, Optional, List + from typing import Optional from bastd.ui.colorpicker import ColorPicker @@ -38,9 +38,9 @@ class EditProfileWindow(ba.Window): self._in_main_menu = in_main_menu self._existing_profile = existing_profile self._r = 'editProfileWindow' - self._spazzes: List[str] = [] - self._icon_textures: List[ba.Texture] = [] - self._icon_tint_textures: List[ba.Texture] = [] + self._spazzes: list[str] = [] + self._icon_textures: list[ba.Texture] = [] + self._icon_tint_textures: list[ba.Texture] = [] # Grab profile colors or pick random ones. self._color, self._highlight = get_player_profile_colors( @@ -523,7 +523,7 @@ class EditProfileWindow(ba.Window): tint_color=self._color, tint2_color=self._highlight) - def _make_picker(self, picker_type: str, origin: Tuple[float, + def _make_picker(self, picker_type: str, origin: tuple[float, float]) -> None: from bastd.ui import colorpicker if picker_type == 'color': @@ -550,12 +550,12 @@ class EditProfileWindow(ba.Window): selected_profile=self._existing_profile, in_main_menu=self._in_main_menu).get_root_widget()) - def _set_color(self, color: Tuple[float, float, float]) -> None: + def _set_color(self, color: tuple[float, float, float]) -> None: self._color = color if self._color_button: ba.buttonwidget(edit=self._color_button, color=color) - def _set_highlight(self, color: Tuple[float, float, float]) -> None: + def _set_highlight(self, color: tuple[float, float, float]) -> None: self._highlight = color if self._highlight_button: ba.buttonwidget(edit=self._highlight_button, color=color) @@ -575,7 +575,7 @@ class EditProfileWindow(ba.Window): print('color_picker_closing got unknown tag ' + str(tag)) def color_picker_selected_color(self, picker: ColorPicker, - color: Tuple[float, float, float]) -> None: + color: tuple[float, float, float]) -> None: """Called when a color is selected in a color picker.""" if not self._root_widget: return diff --git a/dist/ba_data/python/bastd/ui/profile/upgrade.py b/dist/ba_data/python/bastd/ui/profile/upgrade.py index c28777f..5d2c8f1 100644 --- a/dist/ba_data/python/bastd/ui/profile/upgrade.py +++ b/dist/ba_data/python/bastd/ui/profile/upgrade.py @@ -12,7 +12,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Optional, Dict + from typing import Any, Optional from bastd.ui.profile.edit import EditProfileWindow @@ -134,7 +134,7 @@ class ProfileUpgradeWindow(ba.Window): repeat=True) self._update() - def _profile_check_result(self, result: Optional[Dict[str, Any]]) -> None: + def _profile_check_result(self, result: Optional[dict[str, Any]]) -> None: if result is None: ba.textwidget( edit=self._status_text, diff --git a/dist/ba_data/python/bastd/ui/promocode.py b/dist/ba_data/python/bastd/ui/promocode.py index dfd1fdd..ce416a5 100644 --- a/dist/ba_data/python/bastd/ui/promocode.py +++ b/dist/ba_data/python/bastd/ui/promocode.py @@ -11,7 +11,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Optional, Tuple + from typing import Optional class PromoCodeWindow(ba.Window): @@ -19,7 +19,7 @@ class PromoCodeWindow(ba.Window): def __init__(self, modal: bool = False, origin_widget: ba.Widget = None): - scale_origin: Optional[Tuple[float, float]] + scale_origin: Optional[tuple[float, float]] if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() diff --git a/dist/ba_data/python/bastd/ui/purchase.py b/dist/ba_data/python/bastd/ui/purchase.py index 519fb48..c516b43 100644 --- a/dist/ba_data/python/bastd/ui/purchase.py +++ b/dist/ba_data/python/bastd/ui/purchase.py @@ -10,14 +10,14 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Dict, List, Optional + from typing import Any, Optional class PurchaseWindow(ba.Window): """Window for purchasing one or more items.""" def __init__(self, - items: List[str], + items: list[str], transition: str = 'in_right', header_text: ba.Lstr = None): from ba.internal import get_store_item_display_size @@ -50,7 +50,7 @@ class PurchaseWindow(ba.Window): scale=1.2, color=(1, 0.8, 0.3, 1)) size = get_store_item_display_size(items[0]) - display: Dict[str, Any] = {} + display: dict[str, Any] = {} storeitemui.instantiate_store_item_display( items[0], display, diff --git a/dist/ba_data/python/bastd/ui/radiogroup.py b/dist/ba_data/python/bastd/ui/radiogroup.py index 20d3f9c..39f2aff 100644 --- a/dist/ba_data/python/bastd/ui/radiogroup.py +++ b/dist/ba_data/python/bastd/ui/radiogroup.py @@ -9,7 +9,7 @@ from typing import TYPE_CHECKING import ba if TYPE_CHECKING: - from typing import List, Any, Callable, Sequence + from typing import Any, Callable, Sequence def make_radio_group(check_boxes: Sequence[ba.Widget], @@ -17,7 +17,7 @@ def make_radio_group(check_boxes: Sequence[ba.Widget], 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], + def _radio_press(check_string: str, other_check_boxes: list[ba.Widget], val: int) -> None: if val == 1: value_change_call(check_string) diff --git a/dist/ba_data/python/bastd/ui/serverdialog.py b/dist/ba_data/python/bastd/ui/serverdialog.py index 1756503..1f98ef7 100644 --- a/dist/ba_data/python/bastd/ui/serverdialog.py +++ b/dist/ba_data/python/bastd/ui/serverdialog.py @@ -10,13 +10,13 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Dict, Optional + from typing import Any, Optional class ServerDialogWindow(ba.Window): """A dialog window driven by the master-server.""" - def __init__(self, data: Dict[str, Any]): + def __init__(self, data: dict[str, Any]): self._dialog_id = data['dialogID'] txt = ba.Lstr(translate=('serverResponses', data['text']), subs=data.get('subs', [])).evaluate() diff --git a/dist/ba_data/python/bastd/ui/settings/__pycache__/__init__.cpython-38.pyc b/dist/ba_data/python/bastd/ui/settings/__pycache__/__init__.cpython-38.pyc index 1b1ea1a..bf41044 100644 Binary files a/dist/ba_data/python/bastd/ui/settings/__pycache__/__init__.cpython-38.pyc and b/dist/ba_data/python/bastd/ui/settings/__pycache__/__init__.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/ui/settings/__pycache__/__init__.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/settings/__pycache__/__init__.cpython-39.opt-1.pyc new file mode 100644 index 0000000..de6c0cc Binary files /dev/null and b/dist/ba_data/python/bastd/ui/settings/__pycache__/__init__.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/settings/__pycache__/__init__.cpython-39.pyc b/dist/ba_data/python/bastd/ui/settings/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..a178bcb Binary files /dev/null and b/dist/ba_data/python/bastd/ui/settings/__pycache__/__init__.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/ui/settings/__pycache__/advanced.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/ui/settings/__pycache__/advanced.cpython-38.opt-1.pyc index 1cb9422..bc517e8 100644 Binary files a/dist/ba_data/python/bastd/ui/settings/__pycache__/advanced.cpython-38.opt-1.pyc and b/dist/ba_data/python/bastd/ui/settings/__pycache__/advanced.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/settings/__pycache__/advanced.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/settings/__pycache__/advanced.cpython-39.opt-1.pyc new file mode 100644 index 0000000..0b64721 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/settings/__pycache__/advanced.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/settings/__pycache__/allsettings.cpython-38.pyc b/dist/ba_data/python/bastd/ui/settings/__pycache__/allsettings.cpython-38.pyc index 6c7193a..726e804 100644 Binary files a/dist/ba_data/python/bastd/ui/settings/__pycache__/allsettings.cpython-38.pyc and b/dist/ba_data/python/bastd/ui/settings/__pycache__/allsettings.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/ui/settings/__pycache__/allsettings.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/settings/__pycache__/allsettings.cpython-39.opt-1.pyc new file mode 100644 index 0000000..c5ac5b0 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/settings/__pycache__/allsettings.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/settings/__pycache__/allsettings.cpython-39.pyc b/dist/ba_data/python/bastd/ui/settings/__pycache__/allsettings.cpython-39.pyc new file mode 100644 index 0000000..7882e56 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/settings/__pycache__/allsettings.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/ui/settings/__pycache__/audio.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/settings/__pycache__/audio.cpython-39.opt-1.pyc new file mode 100644 index 0000000..e733e01 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/settings/__pycache__/audio.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/settings/__pycache__/controls.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/settings/__pycache__/controls.cpython-39.opt-1.pyc new file mode 100644 index 0000000..2710cf6 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/settings/__pycache__/controls.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/settings/__pycache__/gamepad.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/settings/__pycache__/gamepad.cpython-39.opt-1.pyc new file mode 100644 index 0000000..cd3cff5 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/settings/__pycache__/gamepad.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/settings/__pycache__/gamepadadvanced.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/settings/__pycache__/gamepadadvanced.cpython-39.opt-1.pyc new file mode 100644 index 0000000..4461acb Binary files /dev/null and b/dist/ba_data/python/bastd/ui/settings/__pycache__/gamepadadvanced.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/settings/__pycache__/gamepadselect.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/settings/__pycache__/gamepadselect.cpython-39.opt-1.pyc new file mode 100644 index 0000000..e4a4247 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/settings/__pycache__/gamepadselect.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/settings/__pycache__/graphics.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/settings/__pycache__/graphics.cpython-39.opt-1.pyc new file mode 100644 index 0000000..9e15922 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/settings/__pycache__/graphics.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/settings/__pycache__/keyboard.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/settings/__pycache__/keyboard.cpython-39.opt-1.pyc new file mode 100644 index 0000000..b8ef53c Binary files /dev/null and b/dist/ba_data/python/bastd/ui/settings/__pycache__/keyboard.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/settings/__pycache__/nettesting.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/settings/__pycache__/nettesting.cpython-39.opt-1.pyc new file mode 100644 index 0000000..3d37bcb Binary files /dev/null and b/dist/ba_data/python/bastd/ui/settings/__pycache__/nettesting.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/settings/__pycache__/plugins.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/settings/__pycache__/plugins.cpython-39.opt-1.pyc new file mode 100644 index 0000000..648f310 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/settings/__pycache__/plugins.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/settings/__pycache__/ps3controller.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/settings/__pycache__/ps3controller.cpython-39.opt-1.pyc new file mode 100644 index 0000000..ce01f81 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/settings/__pycache__/ps3controller.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/settings/__pycache__/remoteapp.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/settings/__pycache__/remoteapp.cpython-39.opt-1.pyc new file mode 100644 index 0000000..837168c Binary files /dev/null and b/dist/ba_data/python/bastd/ui/settings/__pycache__/remoteapp.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/settings/__pycache__/testing.cpython-38.opt-1.pyc b/dist/ba_data/python/bastd/ui/settings/__pycache__/testing.cpython-38.opt-1.pyc index 015dbad..100944f 100644 Binary files a/dist/ba_data/python/bastd/ui/settings/__pycache__/testing.cpython-38.opt-1.pyc and b/dist/ba_data/python/bastd/ui/settings/__pycache__/testing.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/settings/__pycache__/testing.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/settings/__pycache__/testing.cpython-39.opt-1.pyc new file mode 100644 index 0000000..60d7329 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/settings/__pycache__/testing.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/settings/__pycache__/touchscreen.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/settings/__pycache__/touchscreen.cpython-39.opt-1.pyc new file mode 100644 index 0000000..e27bbcf Binary files /dev/null and b/dist/ba_data/python/bastd/ui/settings/__pycache__/touchscreen.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/settings/__pycache__/vrtesting.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/settings/__pycache__/vrtesting.cpython-39.opt-1.pyc new file mode 100644 index 0000000..6b5404b Binary files /dev/null and b/dist/ba_data/python/bastd/ui/settings/__pycache__/vrtesting.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/settings/__pycache__/wiimote.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/settings/__pycache__/wiimote.cpython-39.opt-1.pyc new file mode 100644 index 0000000..b5d89a8 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/settings/__pycache__/wiimote.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/settings/__pycache__/xbox360controller.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/settings/__pycache__/xbox360controller.cpython-39.opt-1.pyc new file mode 100644 index 0000000..f03f561 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/settings/__pycache__/xbox360controller.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/settings/advanced.py b/dist/ba_data/python/bastd/ui/settings/advanced.py index 1d78690..d281b4f 100644 --- a/dist/ba_data/python/bastd/ui/settings/advanced.py +++ b/dist/ba_data/python/bastd/ui/settings/advanced.py @@ -11,7 +11,7 @@ import ba from bastd.ui import popup as popup_ui if TYPE_CHECKING: - from typing import Tuple, Any, Optional, List, Dict + from typing import Any, Optional class AdvancedSettingsWindow(ba.Window): @@ -31,7 +31,7 @@ class AdvancedSettingsWindow(ba.Window): app = ba.app # If they provided an origin-widget, scale up from that. - scale_origin: Optional[Tuple[float, float]] + scale_origin: Optional[tuple[float, float]] if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() @@ -57,8 +57,8 @@ class AdvancedSettingsWindow(ba.Window): 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] = [] - self._complete_langs_list: Optional[List] = None + self._prev_lang_list: list[str] = [] + self._complete_langs_list: Optional[list] = None self._complete_langs_error = False self._language_popup: Optional[popup_ui.PopupMenu] = None @@ -258,7 +258,8 @@ 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') as infile: + with open('ba_data/data/langdata.json', + encoding='utf-8') as infile: lang_names_translated = (json.loads( infile.read())['lang_names_translated']) except Exception: @@ -692,7 +693,7 @@ class AdvancedSettingsWindow(ba.Window): self._save_state() ba.timer(0.1, ba.WeakCall(self._rebuild), timetype=ba.TimeType.REAL) - def _completed_langs_cb(self, results: Optional[Dict[str, Any]]) -> None: + def _completed_langs_cb(self, results: Optional[dict[str, Any]]) -> None: if results is not None and results['langs'] is not None: self._complete_langs_list = results['langs'] self._complete_langs_error = False diff --git a/dist/ba_data/python/bastd/ui/settings/allsettings.py b/dist/ba_data/python/bastd/ui/settings/allsettings.py index ede3823..cf44b21 100644 --- a/dist/ba_data/python/bastd/ui/settings/allsettings.py +++ b/dist/ba_data/python/bastd/ui/settings/allsettings.py @@ -10,7 +10,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Tuple, Optional, Union + from typing import Optional, Union class AllSettingsWindow(ba.Window): @@ -28,7 +28,7 @@ class AllSettingsWindow(ba.Window): threading.Thread(target=self._preload_modules).start() ba.set_analytics_screen('Settings Window') - scale_origin: Optional[Tuple[float, float]] + scale_origin: Optional[tuple[float, float]] if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() diff --git a/dist/ba_data/python/bastd/ui/settings/audio.py b/dist/ba_data/python/bastd/ui/settings/audio.py index 99f98d9..0e27e38 100644 --- a/dist/ba_data/python/bastd/ui/settings/audio.py +++ b/dist/ba_data/python/bastd/ui/settings/audio.py @@ -10,7 +10,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Tuple, Optional + from typing import Optional class AudioSettingsWindow(ba.Window): @@ -28,7 +28,7 @@ class AudioSettingsWindow(ba.Window): music = ba.app.music # If they provided an origin-widget, scale up from that. - scale_origin: Optional[Tuple[float, float]] + scale_origin: Optional[tuple[float, float]] if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() diff --git a/dist/ba_data/python/bastd/ui/settings/controls.py b/dist/ba_data/python/bastd/ui/settings/controls.py index ee06d27..81aa071 100644 --- a/dist/ba_data/python/bastd/ui/settings/controls.py +++ b/dist/ba_data/python/bastd/ui/settings/controls.py @@ -10,7 +10,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Tuple, Optional + from typing import Optional class ControlsSettingsWindow(ba.Window): @@ -27,7 +27,7 @@ class ControlsSettingsWindow(ba.Window): from bastd.ui import popup as popup_ui self._have_selected_child = False - scale_origin: Optional[Tuple[float, float]] + scale_origin: Optional[tuple[float, float]] # If they provided an origin-widget, scale up from that. if origin_widget is not None: diff --git a/dist/ba_data/python/bastd/ui/settings/gamepad.py b/dist/ba_data/python/bastd/ui/settings/gamepad.py index 4c34509..b951138 100644 --- a/dist/ba_data/python/bastd/ui/settings/gamepad.py +++ b/dist/ba_data/python/bastd/ui/settings/gamepad.py @@ -10,7 +10,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Dict, Any, Optional, Union, Tuple, Callable + from typing import Any, Optional, Union, Callable class GamepadSettingsWindow(ba.Window): @@ -62,7 +62,7 @@ class GamepadSettingsWindow(ba.Window): for widget in self._root_widget.get_children(): widget.delete() - self._textwidgets: Dict[str, ba.Widget] = {} + self._textwidgets: dict[str, ba.Widget] = {} # If we were supplied with settings, we're a secondary joystick and # just operate on that. in the other (normal) case we make our own. @@ -348,7 +348,7 @@ class GamepadSettingsWindow(ba.Window): """(internal)""" return self._is_secondary - def get_settings(self) -> Dict[str, Any]: + def get_settings(self) -> dict[str, Any]: """(internal)""" assert self._settings is not None return self._settings @@ -528,7 +528,7 @@ class GamepadSettingsWindow(ba.Window): 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], + def _gamepad_event(self, control: str, event: dict[str, Any], dialog: AwaitGamepadInputWindow) -> None: # pylint: disable=too-many-nested-blocks # pylint: disable=too-many-branches @@ -647,8 +647,8 @@ class GamepadSettingsWindow(ba.Window): dialog.die() def _capture_button(self, - pos: Tuple[float, float], - color: Tuple[float, float, float], + pos: tuple[float, float], + color: tuple[float, float, float], texture: ba.Texture, button: str, scale: float = 1.0, @@ -713,7 +713,7 @@ class GamepadSettingsWindow(ba.Window): assert self._settings is not None if self._input: dst = get_input_device_config(self._input, default=True) - dst2: Dict[str, Any] = dst[0][dst[1]] + dst2: dict[str, Any] = dst[0][dst[1]] dst2.clear() # Store any values that aren't -1. @@ -752,7 +752,7 @@ class AwaitGamepadInputWindow(ba.Window): self, gamepad: ba.InputDevice, button: str, - callback: Callable[[str, Dict[str, Any], AwaitGamepadInputWindow], + callback: Callable[[str, dict[str, Any], AwaitGamepadInputWindow], Any], message: ba.Lstr = None, message2: ba.Lstr = None): @@ -815,7 +815,7 @@ class AwaitGamepadInputWindow(ba.Window): if self._root_widget: ba.containerwidget(edit=self._root_widget, transition='out_scale') - def _event_callback(self, event: Dict[str, Any]) -> None: + def _event_callback(self, event: dict[str, Any]) -> None: input_device = event['input_device'] assert isinstance(input_device, ba.InputDevice) diff --git a/dist/ba_data/python/bastd/ui/settings/gamepadadvanced.py b/dist/ba_data/python/bastd/ui/settings/gamepadadvanced.py index 07c1074..116d0fe 100644 --- a/dist/ba_data/python/bastd/ui/settings/gamepadadvanced.py +++ b/dist/ba_data/python/bastd/ui/settings/gamepadadvanced.py @@ -9,7 +9,7 @@ from typing import TYPE_CHECKING import ba if TYPE_CHECKING: - from typing import Dict, Tuple, Optional, Any + from typing import Optional, Any from bastd.ui.settings import gamepad as gpsui @@ -27,7 +27,7 @@ class GamepadAdvancedSettingsWindow(ba.Window): self._width = 900 if uiscale is ba.UIScale.SMALL else 700 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] = {} + self._textwidgets: dict[str, ba.Widget] = {} super().__init__(root_widget=ba.containerwidget( transition='in_scale', size=(self._width, self._height), @@ -331,10 +331,10 @@ class GamepadAdvancedSettingsWindow(ba.Window): def _capture_button( self, - pos: Tuple[float, float], + pos: tuple[float, float], name: ba.Lstr, control: str, - message: Optional[ba.Lstr] = None) -> Tuple[ba.Widget, ba.Widget]: + message: Optional[ba.Lstr] = None) -> tuple[ba.Widget, ba.Widget]: if message is None: message = ba.Lstr(resource=self._parent_window.get_r() + '.pressAnyButtonText') @@ -398,13 +398,13 @@ class GamepadAdvancedSettingsWindow(ba.Window): self, name: ba.Lstr, control: str, - position: Tuple[float, float], + 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) -> Tuple[ba.Widget, ba.Widget]: + displayname: ba.Lstr = None) -> tuple[ba.Widget, ba.Widget]: if displayname is None: displayname = name @@ -455,7 +455,7 @@ class GamepadAdvancedSettingsWindow(ba.Window): 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], + 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]: diff --git a/dist/ba_data/python/bastd/ui/settings/gamepadselect.py b/dist/ba_data/python/bastd/ui/settings/gamepadselect.py index 71b867c..8053fe3 100644 --- a/dist/ba_data/python/bastd/ui/settings/gamepadselect.py +++ b/dist/ba_data/python/bastd/ui/settings/gamepadselect.py @@ -10,10 +10,10 @@ import _ba import ba if TYPE_CHECKING: - from typing import Dict, Any + from typing import Any -def gamepad_configure_callback(event: Dict[str, Any]) -> None: +def gamepad_configure_callback(event: dict[str, Any]) -> None: """Respond to a gamepad button press during config selection.""" from ba.internal import get_remote_app_name from bastd.ui.settings import gamepad diff --git a/dist/ba_data/python/bastd/ui/settings/graphics.py b/dist/ba_data/python/bastd/ui/settings/graphics.py index df3e89c..b4e5955 100644 --- a/dist/ba_data/python/bastd/ui/settings/graphics.py +++ b/dist/ba_data/python/bastd/ui/settings/graphics.py @@ -10,7 +10,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Tuple, Optional + from typing import Optional class GraphicsSettingsWindow(ba.Window): @@ -25,7 +25,7 @@ class GraphicsSettingsWindow(ba.Window): from bastd.ui import popup from bastd.ui.config import ConfigCheckBox, ConfigNumberEdit # if they provided an origin-widget, scale up from that - scale_origin: Optional[Tuple[float, float]] + scale_origin: Optional[tuple[float, float]] if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() diff --git a/dist/ba_data/python/bastd/ui/settings/keyboard.py b/dist/ba_data/python/bastd/ui/settings/keyboard.py index 7482381..42c634c 100644 --- a/dist/ba_data/python/bastd/ui/settings/keyboard.py +++ b/dist/ba_data/python/bastd/ui/settings/keyboard.py @@ -10,7 +10,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Dict, Tuple, Any, Optional + from typing import Any, Optional class ConfigKeyboardWindow(ba.Window): @@ -47,7 +47,7 @@ class ConfigKeyboardWindow(ba.Window): widget.delete() # Fill our temp config with present values. - self._settings: Dict[str, int] = {} + self._settings: dict[str, int] = {} for button in [ 'buttonJump', 'buttonPunch', 'buttonBomb', 'buttonPickUp', 'buttonStart', 'buttonStart2', 'buttonUp', 'buttonDown', @@ -165,8 +165,8 @@ class ConfigKeyboardWindow(ba.Window): scale=1.0) def _capture_button(self, - pos: Tuple[float, float], - color: Tuple[float, float, float], + pos: tuple[float, float], + color: tuple[float, float, float], texture: ba.Texture, button: str, scale: float = 1.0) -> None: @@ -224,7 +224,7 @@ class ConfigKeyboardWindow(ba.Window): return dst = get_input_device_config(self._input, default=False) - dst2: Dict[str, Any] = dst[0][dst[1]] + dst2: dict[str, Any] = dst[0][dst[1]] dst2.clear() # Store any values that aren't -1. @@ -292,7 +292,7 @@ class AwaitKeyboardInputWindow(ba.Window): if self._root_widget: ba.containerwidget(edit=self._root_widget, transition='out_left') - def _button_callback(self, event: Dict[str, Any]) -> None: + def _button_callback(self, event: dict[str, Any]) -> None: self._settings[self._capture_button] = event['button'] if event['type'] == 'BUTTONDOWN': bname = event['input_device'].get_button_name(event['button']) diff --git a/dist/ba_data/python/bastd/ui/settings/plugins.py b/dist/ba_data/python/bastd/ui/settings/plugins.py index 72e4480..13706ef 100644 --- a/dist/ba_data/python/bastd/ui/settings/plugins.py +++ b/dist/ba_data/python/bastd/ui/settings/plugins.py @@ -9,7 +9,7 @@ from typing import TYPE_CHECKING import ba if TYPE_CHECKING: - from typing import Tuple, Optional, Dict + from typing import Optional class PluginSettingsWindow(ba.Window): @@ -22,7 +22,7 @@ class PluginSettingsWindow(ba.Window): app = ba.app # If they provided an origin-widget, scale up from that. - scale_origin: Optional[Tuple[float, float]] + scale_origin: Optional[tuple[float, float]] if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() @@ -98,7 +98,7 @@ class PluginSettingsWindow(ba.Window): color=(1, 0, 0)) ba.playsound(ba.getsound('error')) pluglist = ba.app.plugins.potential_plugins - plugstates: Dict[str, Dict] = ba.app.config.setdefault('Plugins', {}) + plugstates: dict[str, dict] = ba.app.config.setdefault('Plugins', {}) assert isinstance(plugstates, dict) for i, availplug in enumerate(pluglist): active = availplug.class_path in ba.app.plugins.active_plugins @@ -134,7 +134,7 @@ class PluginSettingsWindow(ba.Window): ba.screenmessage( ba.Lstr(resource='settingsWindowAdvanced.mustRestartText'), color=(1.0, 0.5, 0.0)) - plugstates: Dict[str, Dict] = ba.app.config.setdefault('Plugins', {}) + plugstates: dict[str, dict] = ba.app.config.setdefault('Plugins', {}) assert isinstance(plugstates, dict) plugstate = plugstates.setdefault(plug.class_path, {}) plugstate['enabled'] = value diff --git a/dist/ba_data/python/bastd/ui/settings/testing.py b/dist/ba_data/python/bastd/ui/settings/testing.py index a96dd0d..51f237e 100644 --- a/dist/ba_data/python/bastd/ui/settings/testing.py +++ b/dist/ba_data/python/bastd/ui/settings/testing.py @@ -11,7 +11,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Dict, List + from typing import Any class TestingWindow(ba.Window): @@ -19,7 +19,7 @@ class TestingWindow(ba.Window): def __init__(self, title: ba.Lstr, - entries: List[Dict[str, Any]], + entries: list[dict[str, Any]], transition: str = 'in_right'): uiscale = ba.app.ui.uiscale self._width = 600 @@ -115,6 +115,7 @@ class TestingWindow(ba.Window): 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), @@ -145,7 +146,7 @@ class TestingWindow(ba.Window): right_widget=btn, on_activate_call=self._on_reset_press) - def _get_entry(self, name: str) -> Dict[str, Any]: + def _get_entry(self, name: str) -> dict[str, Any]: for entry in self._entries: if entry['name'] == name: return entry @@ -155,18 +156,21 @@ class TestingWindow(ba.Window): for entry in self._entries: _ba.value_test(entry['name'], absolute=ba.app.value_test_defaults[entry['name']]) + # pylint: disable=consider-using-f-string ba.textwidget(edit=entry['widget'], text='%.4g' % _ba.value_test(entry['name'])) def _on_minus_press(self, entry_name: str) -> None: entry = self._get_entry(entry_name) _ba.value_test(entry['name'], change=-entry['increment']) + # pylint: disable=consider-using-f-string ba.textwidget(edit=entry['widget'], text='%.4g' % _ba.value_test(entry['name'])) def _on_plus_press(self, entry_name: str) -> None: entry = self._get_entry(entry_name) _ba.value_test(entry['name'], change=entry['increment']) + # pylint: disable=consider-using-f-string ba.textwidget(edit=entry['widget'], text='%.4g' % _ba.value_test(entry['name'])) diff --git a/dist/ba_data/python/bastd/ui/settings/vrtesting.py b/dist/ba_data/python/bastd/ui/settings/vrtesting.py index 7065d46..a899caa 100644 --- a/dist/ba_data/python/bastd/ui/settings/vrtesting.py +++ b/dist/ba_data/python/bastd/ui/settings/vrtesting.py @@ -10,7 +10,7 @@ import ba from bastd.ui.settings import testing if TYPE_CHECKING: - from typing import Any, Dict, List + from typing import Any class VRTestingWindow(testing.TestingWindow): @@ -18,7 +18,7 @@ class VRTestingWindow(testing.TestingWindow): def __init__(self, transition: str = 'in_right'): - entries: List[Dict[str, Any]] = [] + entries: list[dict[str, Any]] = [] app = ba.app # these are gear-vr only if app.platform == 'android' and app.subplatform == 'oculus': diff --git a/dist/ba_data/python/bastd/ui/soundtrack/__pycache__/__init__.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/soundtrack/__pycache__/__init__.cpython-39.opt-1.pyc new file mode 100644 index 0000000..de6c0cc Binary files /dev/null and b/dist/ba_data/python/bastd/ui/soundtrack/__pycache__/__init__.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/soundtrack/__pycache__/browser.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/soundtrack/__pycache__/browser.cpython-39.opt-1.pyc new file mode 100644 index 0000000..cf357ae Binary files /dev/null and b/dist/ba_data/python/bastd/ui/soundtrack/__pycache__/browser.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/soundtrack/__pycache__/edit.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/soundtrack/__pycache__/edit.cpython-39.opt-1.pyc new file mode 100644 index 0000000..b15b7b3 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/soundtrack/__pycache__/edit.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/soundtrack/__pycache__/entrytypeselect.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/soundtrack/__pycache__/entrytypeselect.cpython-39.opt-1.pyc new file mode 100644 index 0000000..eb11e43 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/soundtrack/__pycache__/entrytypeselect.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/soundtrack/__pycache__/macmusicapp.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/soundtrack/__pycache__/macmusicapp.cpython-39.opt-1.pyc new file mode 100644 index 0000000..fdcc188 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/soundtrack/__pycache__/macmusicapp.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/soundtrack/browser.py b/dist/ba_data/python/bastd/ui/soundtrack/browser.py index 7989d32..cc12517 100644 --- a/dist/ba_data/python/bastd/ui/soundtrack/browser.py +++ b/dist/ba_data/python/bastd/ui/soundtrack/browser.py @@ -11,7 +11,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Optional, List, Tuple, Dict + from typing import Any, Optional class SoundtrackBrowserWindow(ba.Window): @@ -24,7 +24,7 @@ class SoundtrackBrowserWindow(ba.Window): # pylint: disable=too-many-statements # If they provided an origin-widget, scale up from that. - scale_origin: Optional[Tuple[float, float]] + scale_origin: Optional[tuple[float, float]] if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() @@ -81,7 +81,7 @@ class SoundtrackBrowserWindow(ba.Window): b_color = (0.6, 0.53, 0.63) b_textcolor = (0.75, 0.7, 0.8) lock_tex = ba.gettexture('lock') - self._lock_images: List[ba.Widget] = [] + 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) @@ -195,10 +195,10 @@ class SoundtrackBrowserWindow(ba.Window): if ba.app.ui.use_toolbars else self._scrollwidget) self._col = ba.columnwidget(parent=scrollwidget, border=2, margin=0) - self._soundtracks: Optional[Dict[str, Any]] = None + self._soundtracks: Optional[dict[str, Any]] = None self._selected_soundtrack: Optional[str] = None self._selected_soundtrack_index: Optional[int] = None - self._soundtrack_widgets: List[ba.Widget] = [] + self._soundtrack_widgets: list[ba.Widget] = [] self._allow_changing_soundtracks = False self._refresh() if self._back_button is not None: @@ -259,7 +259,7 @@ class SoundtrackBrowserWindow(ba.Window): if self._selected_soundtrack is None: return - sdtk: Dict[str, Any] + sdtk: dict[str, Any] if self._selected_soundtrack == '__default__': sdtk = {} else: diff --git a/dist/ba_data/python/bastd/ui/soundtrack/edit.py b/dist/ba_data/python/bastd/ui/soundtrack/edit.py index e264d11..7e08f0f 100644 --- a/dist/ba_data/python/bastd/ui/soundtrack/edit.py +++ b/dist/ba_data/python/bastd/ui/soundtrack/edit.py @@ -11,14 +11,14 @@ from typing import TYPE_CHECKING, cast import ba if TYPE_CHECKING: - from typing import Any, Dict, Union, Optional + from typing import Any, Union, Optional class SoundtrackEditWindow(ba.Window): """Window for editing a soundtrack.""" def __init__(self, - existing_soundtrack: Optional[Union[str, Dict[str, Any]]], + existing_soundtrack: Optional[Union[str, dict[str, Any]]], transition: str = 'in_right'): # pylint: disable=too-many-statements appconfig = ba.app.config @@ -147,7 +147,7 @@ class SoundtrackEditWindow(ba.Window): claims_tab=True, selection_loops_to_parent=True) - self._song_type_buttons: Dict[str, ba.Widget] = {} + self._song_type_buttons: dict[str, ba.Widget] = {} self._refresh() ba.buttonwidget(edit=cancel_button, on_activate_call=self._cancel) ba.containerwidget(edit=self._root_widget, cancel_button=cancel_button) @@ -266,7 +266,7 @@ class SoundtrackEditWindow(ba.Window): prev_test_button = btn @classmethod - def _restore_editor(cls, state: Dict[str, Any], musictype: str, + def _restore_editor(cls, state: dict[str, Any], musictype: str, entry: Any) -> None: music = ba.app.music diff --git a/dist/ba_data/python/bastd/ui/soundtrack/macmusicapp.py b/dist/ba_data/python/bastd/ui/soundtrack/macmusicapp.py index 1f149b0..ff50e6b 100644 --- a/dist/ba_data/python/bastd/ui/soundtrack/macmusicapp.py +++ b/dist/ba_data/python/bastd/ui/soundtrack/macmusicapp.py @@ -10,7 +10,7 @@ from typing import TYPE_CHECKING import ba if TYPE_CHECKING: - from typing import Any, List, Optional, Callable + from typing import Any, Optional, Callable class MacMusicAppPlaylistSelectWindow(ba.Window): @@ -66,7 +66,7 @@ class MacMusicAppPlaylistSelectWindow(ba.Window): ba.containerwidget(edit=self._root_widget, selected_child=self._scrollwidget) - def _playlists_cb(self, playlists: List[str]) -> None: + def _playlists_cb(self, playlists: list[str]) -> None: if self._column: for widget in self._column.get_children(): widget.delete() diff --git a/dist/ba_data/python/bastd/ui/specialoffer.py b/dist/ba_data/python/bastd/ui/specialoffer.py index 3e3a542..5b2c2ab 100644 --- a/dist/ba_data/python/bastd/ui/specialoffer.py +++ b/dist/ba_data/python/bastd/ui/specialoffer.py @@ -11,13 +11,13 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Dict, Optional, Union + from typing import Any, Optional, Union class SpecialOfferWindow(ba.Window): """Window for presenting sales/etc.""" - def __init__(self, offer: Dict[str, Any], transition: str = 'in_right'): + def __init__(self, offer: dict[str, Any], transition: str = 'in_right'): # pylint: disable=too-many-statements # pylint: disable=too-many-branches # pylint: disable=too-many-locals @@ -159,7 +159,7 @@ class SpecialOfferWindow(ba.Window): timetype=ba.TimeType.REAL) size = get_store_item_display_size(self._offer_item) - display: Dict[str, Any] = {} + display: dict[str, Any] = {} storeitemui.instantiate_store_item_display( self._offer_item, display, diff --git a/dist/ba_data/python/bastd/ui/store/__pycache__/__init__.cpython-38.pyc b/dist/ba_data/python/bastd/ui/store/__pycache__/__init__.cpython-38.pyc index 70f7e5d..79aef19 100644 Binary files a/dist/ba_data/python/bastd/ui/store/__pycache__/__init__.cpython-38.pyc and b/dist/ba_data/python/bastd/ui/store/__pycache__/__init__.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/ui/store/__pycache__/__init__.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/store/__pycache__/__init__.cpython-39.opt-1.pyc new file mode 100644 index 0000000..de6c0cc Binary files /dev/null and b/dist/ba_data/python/bastd/ui/store/__pycache__/__init__.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/store/__pycache__/__init__.cpython-39.pyc b/dist/ba_data/python/bastd/ui/store/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..fac4d1f Binary files /dev/null and b/dist/ba_data/python/bastd/ui/store/__pycache__/__init__.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/ui/store/__pycache__/browser.cpython-38.pyc b/dist/ba_data/python/bastd/ui/store/__pycache__/browser.cpython-38.pyc index 1a45d01..e94e6e4 100644 Binary files a/dist/ba_data/python/bastd/ui/store/__pycache__/browser.cpython-38.pyc and b/dist/ba_data/python/bastd/ui/store/__pycache__/browser.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/ui/store/__pycache__/browser.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/store/__pycache__/browser.cpython-39.opt-1.pyc new file mode 100644 index 0000000..4c606cb Binary files /dev/null and b/dist/ba_data/python/bastd/ui/store/__pycache__/browser.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/store/__pycache__/browser.cpython-39.pyc b/dist/ba_data/python/bastd/ui/store/__pycache__/browser.cpython-39.pyc new file mode 100644 index 0000000..384e3a6 Binary files /dev/null and b/dist/ba_data/python/bastd/ui/store/__pycache__/browser.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/ui/store/__pycache__/button.cpython-38.pyc b/dist/ba_data/python/bastd/ui/store/__pycache__/button.cpython-38.pyc index ed8784d..5a88cd2 100644 Binary files a/dist/ba_data/python/bastd/ui/store/__pycache__/button.cpython-38.pyc and b/dist/ba_data/python/bastd/ui/store/__pycache__/button.cpython-38.pyc differ diff --git a/dist/ba_data/python/bastd/ui/store/__pycache__/button.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/store/__pycache__/button.cpython-39.opt-1.pyc new file mode 100644 index 0000000..f57a2da Binary files /dev/null and b/dist/ba_data/python/bastd/ui/store/__pycache__/button.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/store/__pycache__/button.cpython-39.pyc b/dist/ba_data/python/bastd/ui/store/__pycache__/button.cpython-39.pyc new file mode 100644 index 0000000..6cb743d Binary files /dev/null and b/dist/ba_data/python/bastd/ui/store/__pycache__/button.cpython-39.pyc differ diff --git a/dist/ba_data/python/bastd/ui/store/__pycache__/item.cpython-39.opt-1.pyc b/dist/ba_data/python/bastd/ui/store/__pycache__/item.cpython-39.opt-1.pyc new file mode 100644 index 0000000..656670a Binary files /dev/null and b/dist/ba_data/python/bastd/ui/store/__pycache__/item.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/bastd/ui/store/browser.py b/dist/ba_data/python/bastd/ui/store/browser.py index 990f406..6d757c2 100644 --- a/dist/ba_data/python/bastd/ui/store/browser.py +++ b/dist/ba_data/python/bastd/ui/store/browser.py @@ -14,8 +14,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import (Any, Callable, Optional, Tuple, Dict, Union, Sequence, - List) + from typing import Any, Callable, Optional, Union, Sequence class StoreBrowserWindow(ba.Window): @@ -46,7 +45,7 @@ class StoreBrowserWindow(ba.Window): ba.set_analytics_screen('Store Window') - scale_origin: Optional[Tuple[float, float]] + scale_origin: Optional[tuple[float, float]] # If they provided an origin-widget, scale up from that. if origin_widget is not None: @@ -57,7 +56,7 @@ class StoreBrowserWindow(ba.Window): self._transition_out = 'out_right' scale_origin = None - self.button_infos: Optional[Dict[str, Dict[str, Any]]] = None + self.button_infos: Optional[dict[str, dict[str, Any]]] = None self.update_buttons_timer: Optional[ba.Timer] = None self._status_textwidget_update_timer = None @@ -185,8 +184,8 @@ class StoreBrowserWindow(ba.Window): 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(): @@ -388,7 +387,7 @@ class StoreBrowserWindow(ba.Window): ba.WeakCall(self._on_response, data), timetype=ba.TimeType.REAL) - def _on_response(self, data: Optional[Dict[str, Any]]) -> None: + def _on_response(self, data: Optional[dict[str, Any]]) -> None: # FIXME: clean this up. # pylint: disable=protected-access window = self._window() @@ -402,7 +401,7 @@ class StoreBrowserWindow(ba.Window): # Actually start the purchase locally. def _purchase_check_result(self, item: str, is_ticket_purchase: bool, - result: Optional[Dict[str, Any]]) -> None: + result: Optional[dict[str, Any]]) -> None: if result is None: ba.playsound(ba.getsound('error')) ba.screenmessage( @@ -685,7 +684,7 @@ class StoreBrowserWindow(ba.Window): ba.textwidget(edit=b_info['descriptionText'], color=description_color) - def _on_response(self, data: Optional[Dict[str, Any]]) -> None: + def _on_response(self, data: Optional[dict[str, Any]]) -> None: # pylint: disable=too-many-statements # clear status text.. @@ -710,7 +709,7 @@ class StoreBrowserWindow(ba.Window): class _Store: def __init__(self, store_window: StoreBrowserWindow, - sdata: Dict[str, Any], width: float): + sdata: dict[str, Any], width: float): from ba.internal import (get_store_item_display_size, get_store_layout) self._store_window = store_window @@ -855,7 +854,7 @@ class StoreBrowserWindow(ba.Window): maxwidth=700, transition_delay=0.4) - prev_row_buttons: Optional[List] = None + prev_row_buttons: Optional[list] = None this_row_buttons = [] delay = 0.3 @@ -881,7 +880,7 @@ class StoreBrowserWindow(ba.Window): math.floor((self._width - boffs_h - 20) / (b_width + button_spacing))) col = 0 - item: Dict[str, Any] + 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[ diff --git a/dist/ba_data/python/bastd/ui/store/item.py b/dist/ba_data/python/bastd/ui/store/item.py index 841ae1f..ea49ded 100644 --- a/dist/ba_data/python/bastd/ui/store/item.py +++ b/dist/ba_data/python/bastd/ui/store/item.py @@ -9,13 +9,13 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Tuple, Dict, Optional + from typing import Any, Optional def instantiate_store_item_display(item_name: str, - item: Dict[str, Any], + item: dict[str, Any], parent_widget: ba.Widget, - b_pos: Tuple[float, float], + b_pos: tuple[float, float], b_width: float, b_height: float, boffs_h: float = 0.0, diff --git a/dist/ba_data/python/bastd/ui/tabs.py b/dist/ba_data/python/bastd/ui/tabs.py index 62e8a2f..b3dc38a 100644 --- a/dist/ba_data/python/bastd/ui/tabs.py +++ b/dist/ba_data/python/bastd/ui/tabs.py @@ -10,15 +10,15 @@ from typing import TYPE_CHECKING, TypeVar, Generic import ba if TYPE_CHECKING: - from typing import Any, Callable, Dict, Tuple, List, Sequence, Optional + from typing import Any, Callable, Optional @dataclass class Tab: """Info for an individual tab in a TabRow""" button: ba.Widget - position: Tuple[float, float] - size: Tuple[float, float] + position: tuple[float, float] + size: tuple[float, float] T = TypeVar('T') @@ -32,13 +32,13 @@ class TabRow(Generic[T]): def __init__(self, parent: ba.Widget, - tabdefs: List[Tuple[T, ba.Lstr]], - pos: Tuple[float, float], - size: Tuple[float, float], + tabdefs: list[tuple[T, ba.Lstr]], + pos: tuple[float, float], + size: tuple[float, float], on_select_call: Callable[[T], None] = None) -> None: if not tabdefs: raise ValueError('At least one tab def is required') - self.tabs: Dict[T, Tab] = {} + self.tabs: dict[T, Tab] = {} tab_pos_v = pos[1] tab_button_width = float(size[0]) / len(tabdefs) tab_spacing = (250.0 - tab_button_width) * 0.06 diff --git a/dist/ba_data/python/bastd/ui/teamnamescolors.py b/dist/ba_data/python/bastd/ui/teamnamescolors.py index 9f8450e..fc78971 100644 --- a/dist/ba_data/python/bastd/ui/teamnamescolors.py +++ b/dist/ba_data/python/bastd/ui/teamnamescolors.py @@ -10,14 +10,14 @@ import ba from bastd.ui import popup if TYPE_CHECKING: - from typing import Tuple, List, Sequence + from typing import Sequence from bastd.ui.colorpicker import ColorPicker class TeamNamesColorsWindow(popup.PopupWindow): """A popup window for customizing team names and colors.""" - def __init__(self, scale_origin: Tuple[float, float]): + def __init__(self, scale_origin: tuple[float, float]): from ba.internal import DEFAULT_TEAM_COLORS, DEFAULT_TEAM_NAMES self._width = 500 self._height = 330 @@ -44,8 +44,8 @@ class TeamNamesColorsWindow(popup.PopupWindow): self._colors = list( appconfig.get('Custom Team Colors', DEFAULT_TEAM_COLORS)) - self._color_buttons: List[ba.Widget] = [] - self._color_text_fields: List[ba.Widget] = [] + self._color_buttons: list[ba.Widget] = [] + self._color_text_fields: list[ba.Widget] = [] resetbtn = ba.buttonwidget( parent=self.root_widget, @@ -145,7 +145,7 @@ class TeamNamesColorsWindow(popup.PopupWindow): # either the default raw value or its translation we consider it # default. (the fact that team names get translated makes this # situation a bit sloppy) - new_names: List[str] = [] + new_names: list[str] = [] is_default = True for i in range(2): name = cast(str, ba.textwidget(query=self._color_text_fields[i])) diff --git a/dist/ba_data/python/bastd/ui/tournamententry.py b/dist/ba_data/python/bastd/ui/tournamententry.py index 12ed8b3..1737387 100644 --- a/dist/ba_data/python/bastd/ui/tournamententry.py +++ b/dist/ba_data/python/bastd/ui/tournamententry.py @@ -11,7 +11,7 @@ import ba from bastd.ui import popup if TYPE_CHECKING: - from typing import Any, Tuple, Callable, Optional, Dict + from typing import Any, Callable, Optional class TournamentEntryWindow(popup.PopupWindow): @@ -20,10 +20,10 @@ class TournamentEntryWindow(popup.PopupWindow): def __init__(self, tournament_id: str, tournament_activity: ba.Activity = None, - position: Tuple[float, float] = (0.0, 0.0), + position: tuple[float, float] = (0.0, 0.0), delegate: Any = None, scale: float = None, - offset: Tuple[float, float] = (0.0, 0.0), + offset: tuple[float, float] = (0.0, 0.0), on_close_call: Callable[[], Any] = None): # Needs some tidying. # pylint: disable=too-many-branches @@ -302,7 +302,7 @@ class TournamentEntryWindow(popup.PopupWindow): self._update() self._restore_state() - def _on_tournament_query_response(self, data: Optional[Dict[str, + def _on_tournament_query_response(self, data: Optional[dict[str, Any]]) -> None: accounts = ba.app.accounts self._running_query = False diff --git a/dist/ba_data/python/bastd/ui/tournamentscores.py b/dist/ba_data/python/bastd/ui/tournamentscores.py index e162b47..ec704d9 100644 --- a/dist/ba_data/python/bastd/ui/tournamentscores.py +++ b/dist/ba_data/python/bastd/ui/tournamentscores.py @@ -11,7 +11,7 @@ import ba from bastd.ui import popup as popup_ui if TYPE_CHECKING: - from typing import Any, Tuple, Sequence, Callable, Dict, Optional, List + from typing import Any, Sequence, Callable, Optional class TournamentScoresWindow(popup_ui.PopupWindow): @@ -20,9 +20,9 @@ class TournamentScoresWindow(popup_ui.PopupWindow): def __init__(self, tournament_id: str, tournament_activity: ba.GameActivity = None, - position: Tuple[float, float] = (0.0, 0.0), + position: tuple[float, float] = (0.0, 0.0), scale: float = None, - offset: Tuple[float, float] = (0.0, 0.0), + 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, @@ -107,11 +107,11 @@ class TournamentScoresWindow(popup_ui.PopupWindow): callback=ba.WeakCall( self._on_tournament_query_response)) - def _on_tournament_query_response(self, data: Optional[Dict[str, + def _on_tournament_query_response(self, data: Optional[dict[str, Any]]) -> None: if data is not None: # this used to be the whole payload - data_t: List[Dict[str, Any]] = data['t'] + data_t: list[dict[str, Any]] = data['t'] # kill our loading text if we've got scores.. otherwise just # replace it with 'no scores yet' if data_t[0]['scores']: diff --git a/dist/ba_data/python/bastd/ui/trophies.py b/dist/ba_data/python/bastd/ui/trophies.py index 4763172..4e0ceac 100644 --- a/dist/ba_data/python/bastd/ui/trophies.py +++ b/dist/ba_data/python/bastd/ui/trophies.py @@ -10,15 +10,15 @@ import ba from bastd.ui import popup if TYPE_CHECKING: - from typing import Any, Tuple, Dict, List + from typing import Any class TrophiesWindow(popup.PopupWindow): """Popup window for viewing trophies.""" def __init__(self, - position: Tuple[float, float], - data: Dict[str, Any], + position: tuple[float, float], + data: dict[str, Any], scale: float = None): self._data = data uiscale = ba.app.ui.uiscale @@ -110,7 +110,7 @@ class TrophiesWindow(popup.PopupWindow): 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: + trophy_types: list[list[str]]) -> int: from ba.internal import get_trophy_string pts = 0 for i, trophy_type in enumerate(trophy_types): diff --git a/dist/ba_data/python/bastd/ui/watch.py b/dist/ba_data/python/bastd/ui/watch.py index b27a381..c7e871e 100644 --- a/dist/ba_data/python/bastd/ui/watch.py +++ b/dist/ba_data/python/bastd/ui/watch.py @@ -12,7 +12,7 @@ import _ba import ba if TYPE_CHECKING: - from typing import Any, Optional, Tuple, Dict + from typing import Any, Optional class WatchWindow(ba.Window): @@ -30,7 +30,7 @@ class WatchWindow(ba.Window): # pylint: disable=too-many-statements from bastd.ui.tabs import TabRow ba.set_analytics_screen('Watch Window') - scale_origin: Optional[Tuple[float, float]] + scale_origin: Optional[tuple[float, float]] if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() @@ -39,7 +39,7 @@ class WatchWindow(ba.Window): self._transition_out = 'out_right' scale_origin = None ba.app.ui.set_main_menu_location('Watch') - self._tab_data: Dict[str, Any] = {} + self._tab_data: dict[str, Any] = {} self._my_replays_scroll_width: Optional[float] = None self._my_replays_watch_replay_button: Optional[ba.Widget] = None self._scrollwidget: Optional[ba.Widget] = None diff --git a/dist/ba_data/python/efro/__pycache__/__init__.cpython-38.pyc b/dist/ba_data/python/efro/__pycache__/__init__.cpython-38.pyc index 9fd2721..067d8c3 100644 Binary files a/dist/ba_data/python/efro/__pycache__/__init__.cpython-38.pyc and b/dist/ba_data/python/efro/__pycache__/__init__.cpython-38.pyc differ diff --git a/dist/ba_data/python/efro/__pycache__/__init__.cpython-39.opt-1.pyc b/dist/ba_data/python/efro/__pycache__/__init__.cpython-39.opt-1.pyc new file mode 100644 index 0000000..e4501dc Binary files /dev/null and b/dist/ba_data/python/efro/__pycache__/__init__.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/efro/__pycache__/__init__.cpython-39.pyc b/dist/ba_data/python/efro/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..8f3b7c7 Binary files /dev/null and b/dist/ba_data/python/efro/__pycache__/__init__.cpython-39.pyc differ diff --git a/dist/ba_data/python/efro/__pycache__/call.cpython-39.opt-1.pyc b/dist/ba_data/python/efro/__pycache__/call.cpython-39.opt-1.pyc new file mode 100644 index 0000000..b04b064 Binary files /dev/null and b/dist/ba_data/python/efro/__pycache__/call.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/efro/__pycache__/error.cpython-38.opt-1.pyc b/dist/ba_data/python/efro/__pycache__/error.cpython-38.opt-1.pyc index 95d2650..2d11baf 100644 Binary files a/dist/ba_data/python/efro/__pycache__/error.cpython-38.opt-1.pyc and b/dist/ba_data/python/efro/__pycache__/error.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python/efro/__pycache__/error.cpython-38.pyc b/dist/ba_data/python/efro/__pycache__/error.cpython-38.pyc index e178660..1760ee3 100644 Binary files a/dist/ba_data/python/efro/__pycache__/error.cpython-38.pyc and b/dist/ba_data/python/efro/__pycache__/error.cpython-38.pyc differ diff --git a/dist/ba_data/python/efro/__pycache__/error.cpython-39.opt-1.pyc b/dist/ba_data/python/efro/__pycache__/error.cpython-39.opt-1.pyc new file mode 100644 index 0000000..17151e2 Binary files /dev/null and b/dist/ba_data/python/efro/__pycache__/error.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/efro/__pycache__/error.cpython-39.pyc b/dist/ba_data/python/efro/__pycache__/error.cpython-39.pyc new file mode 100644 index 0000000..f7f209c Binary files /dev/null and b/dist/ba_data/python/efro/__pycache__/error.cpython-39.pyc differ diff --git a/dist/ba_data/python/efro/__pycache__/message.cpython-38.opt-1.pyc b/dist/ba_data/python/efro/__pycache__/message.cpython-38.opt-1.pyc new file mode 100644 index 0000000..31f7b5c Binary files /dev/null and b/dist/ba_data/python/efro/__pycache__/message.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python/efro/__pycache__/message.cpython-39.opt-1.pyc b/dist/ba_data/python/efro/__pycache__/message.cpython-39.opt-1.pyc new file mode 100644 index 0000000..8255cd0 Binary files /dev/null and b/dist/ba_data/python/efro/__pycache__/message.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/efro/__pycache__/terminal.cpython-38.pyc b/dist/ba_data/python/efro/__pycache__/terminal.cpython-38.pyc index 99a9d50..c583649 100644 Binary files a/dist/ba_data/python/efro/__pycache__/terminal.cpython-38.pyc and b/dist/ba_data/python/efro/__pycache__/terminal.cpython-38.pyc differ diff --git a/dist/ba_data/python/efro/__pycache__/terminal.cpython-39.opt-1.pyc b/dist/ba_data/python/efro/__pycache__/terminal.cpython-39.opt-1.pyc new file mode 100644 index 0000000..72379c2 Binary files /dev/null and b/dist/ba_data/python/efro/__pycache__/terminal.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/efro/__pycache__/terminal.cpython-39.pyc b/dist/ba_data/python/efro/__pycache__/terminal.cpython-39.pyc new file mode 100644 index 0000000..91901b5 Binary files /dev/null and b/dist/ba_data/python/efro/__pycache__/terminal.cpython-39.pyc differ diff --git a/dist/ba_data/python/efro/__pycache__/util.cpython-38.opt-1.pyc b/dist/ba_data/python/efro/__pycache__/util.cpython-38.opt-1.pyc index cedc7b7..4c79fc1 100644 Binary files a/dist/ba_data/python/efro/__pycache__/util.cpython-38.opt-1.pyc and b/dist/ba_data/python/efro/__pycache__/util.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python/efro/__pycache__/util.cpython-38.pyc b/dist/ba_data/python/efro/__pycache__/util.cpython-38.pyc index 769f1da..ae9a364 100644 Binary files a/dist/ba_data/python/efro/__pycache__/util.cpython-38.pyc and b/dist/ba_data/python/efro/__pycache__/util.cpython-38.pyc differ diff --git a/dist/ba_data/python/efro/__pycache__/util.cpython-39.opt-1.pyc b/dist/ba_data/python/efro/__pycache__/util.cpython-39.opt-1.pyc new file mode 100644 index 0000000..a29243a Binary files /dev/null and b/dist/ba_data/python/efro/__pycache__/util.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/efro/__pycache__/util.cpython-39.pyc b/dist/ba_data/python/efro/__pycache__/util.cpython-39.pyc new file mode 100644 index 0000000..97d4575 Binary files /dev/null and b/dist/ba_data/python/efro/__pycache__/util.cpython-39.pyc differ diff --git a/dist/ba_data/python/efro/call.py b/dist/ba_data/python/efro/call.py index e83f22a..1ea3d98 100644 --- a/dist/ba_data/python/efro/call.py +++ b/dist/ba_data/python/efro/call.py @@ -5,6 +5,7 @@ from __future__ import annotations from typing import TYPE_CHECKING, TypeVar, Generic, Callable, cast +import functools if TYPE_CHECKING: from typing import Any, overload @@ -265,4 +266,8 @@ if TYPE_CHECKING: def Call(*_args: Any, **_keywds: Any) -> Any: ... - Call = Call + # A convenient wrapper around functools.partial which adds type-safety + # (though it does not support keyword arguments). + partial = Call +else: + partial = functools.partial diff --git a/dist/ba_data/python/efro/dataclassio/__init__.py b/dist/ba_data/python/efro/dataclassio/__init__.py new file mode 100644 index 0000000..82e46ad --- /dev/null +++ b/dist/ba_data/python/efro/dataclassio/__init__.py @@ -0,0 +1,137 @@ +# Released under the MIT License. See LICENSE for details. +# +"""Functionality for importing, exporting, and validating dataclasses. + +This allows complex nested dataclasses to be flattened to json-compatible +data and restored from said data. It also gracefully handles and preserves +unrecognized attribute data, allowing older clients to interact with newer +data formats in a nondestructive manner. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, TypeVar + +from efro.dataclassio._outputter import _Outputter +from efro.dataclassio._inputter import _Inputter +from efro.dataclassio._base import Codec, IOAttrs +from efro.dataclassio._prep import ioprep, ioprepped, is_ioprepped_dataclass +from efro.dataclassio._pathcapture import DataclassFieldLookup + +if TYPE_CHECKING: + from typing import Any + +__all__ = [ + 'Codec', 'IOAttrs', 'ioprep', 'ioprepped', 'is_ioprepped_dataclass', + 'DataclassFieldLookup', 'dataclass_to_dict', 'dataclass_to_json', + 'dataclass_from_dict', 'dataclass_from_json', 'dataclass_validate' +] + +T = TypeVar('T') + + +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 + on fields. Note that a limited set of types and data configurations is + supported. + + Values with type Any will be checked to ensure they match types supported + directly by json. This does not include types such as tuples which are + implicitly translated by Python's json module (as this would break + the ability to do a lossless round-trip with data). + + If coerce_to_float is True, integer values present on float typed fields + will be converted to floats in the dict output. If False, a TypeError + will be triggered. + """ + + 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) -> str: + """Utility function; return a json string from a dataclass instance. + + Basically json.dumps(dataclass_to_dict(...)). + """ + import json + jdict = dataclass_to_dict(obj=obj, + coerce_to_float=coerce_to_float, + codec=Codec.JSON) + if pretty: + return json.dumps(jdict, indent=2, sort_keys=True) + return json.dumps(jdict, separators=(',', ':')) + + +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 + json-friendly object types). This means that sequence values such as + tuples or sets should be passed as lists, enums should be passed as their + associated values, nested dataclasses should be passed as dicts, etc. + + All values are checked to ensure their types/values are valid. + + Data for attributes of type Any will be checked to ensure they match + types supported directly by json. This does not include types such + as tuples which are implicitly translated by Python's json module + (as this would break the ability to do a lossless round-trip with data). + + If coerce_to_float is True, int values passed for float typed fields + will be converted to float values. Otherwise a TypeError is raised. + + If allow_unknown_attrs is False, AttributeErrors will be raised for + attributes present in the dict but not on the data class. Otherwise they + will be preserved as part of the instance and included if it is + 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) + + +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) + + +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() diff --git a/dist/ba_data/python/efro/dataclassio/__pycache__/__init__.cpython-38.opt-1.pyc b/dist/ba_data/python/efro/dataclassio/__pycache__/__init__.cpython-38.opt-1.pyc new file mode 100644 index 0000000..35a86b3 Binary files /dev/null and b/dist/ba_data/python/efro/dataclassio/__pycache__/__init__.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python/efro/dataclassio/__pycache__/__init__.cpython-38.pyc b/dist/ba_data/python/efro/dataclassio/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..c2a3de1 Binary files /dev/null and b/dist/ba_data/python/efro/dataclassio/__pycache__/__init__.cpython-38.pyc differ diff --git a/dist/ba_data/python/efro/dataclassio/__pycache__/__init__.cpython-39.opt-1.pyc b/dist/ba_data/python/efro/dataclassio/__pycache__/__init__.cpython-39.opt-1.pyc new file mode 100644 index 0000000..61d876e Binary files /dev/null and b/dist/ba_data/python/efro/dataclassio/__pycache__/__init__.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/efro/dataclassio/__pycache__/__init__.cpython-39.pyc b/dist/ba_data/python/efro/dataclassio/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..ab09024 Binary files /dev/null and b/dist/ba_data/python/efro/dataclassio/__pycache__/__init__.cpython-39.pyc differ diff --git a/dist/ba_data/python/efro/dataclassio/__pycache__/_base.cpython-38.opt-1.pyc b/dist/ba_data/python/efro/dataclassio/__pycache__/_base.cpython-38.opt-1.pyc new file mode 100644 index 0000000..7719791 Binary files /dev/null and b/dist/ba_data/python/efro/dataclassio/__pycache__/_base.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python/efro/dataclassio/__pycache__/_base.cpython-38.pyc b/dist/ba_data/python/efro/dataclassio/__pycache__/_base.cpython-38.pyc new file mode 100644 index 0000000..7686658 Binary files /dev/null and b/dist/ba_data/python/efro/dataclassio/__pycache__/_base.cpython-38.pyc differ diff --git a/dist/ba_data/python/efro/dataclassio/__pycache__/_base.cpython-39.opt-1.pyc b/dist/ba_data/python/efro/dataclassio/__pycache__/_base.cpython-39.opt-1.pyc new file mode 100644 index 0000000..840f7f1 Binary files /dev/null and b/dist/ba_data/python/efro/dataclassio/__pycache__/_base.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/efro/dataclassio/__pycache__/_base.cpython-39.pyc b/dist/ba_data/python/efro/dataclassio/__pycache__/_base.cpython-39.pyc new file mode 100644 index 0000000..559cd12 Binary files /dev/null and b/dist/ba_data/python/efro/dataclassio/__pycache__/_base.cpython-39.pyc differ diff --git a/dist/ba_data/python/efro/dataclassio/__pycache__/_inputter.cpython-38.opt-1.pyc b/dist/ba_data/python/efro/dataclassio/__pycache__/_inputter.cpython-38.opt-1.pyc new file mode 100644 index 0000000..787cca2 Binary files /dev/null and b/dist/ba_data/python/efro/dataclassio/__pycache__/_inputter.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python/efro/dataclassio/__pycache__/_inputter.cpython-38.pyc b/dist/ba_data/python/efro/dataclassio/__pycache__/_inputter.cpython-38.pyc new file mode 100644 index 0000000..f71efc0 Binary files /dev/null and b/dist/ba_data/python/efro/dataclassio/__pycache__/_inputter.cpython-38.pyc differ diff --git a/dist/ba_data/python/efro/dataclassio/__pycache__/_inputter.cpython-39.opt-1.pyc b/dist/ba_data/python/efro/dataclassio/__pycache__/_inputter.cpython-39.opt-1.pyc new file mode 100644 index 0000000..22ad7b4 Binary files /dev/null and b/dist/ba_data/python/efro/dataclassio/__pycache__/_inputter.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/efro/dataclassio/__pycache__/_inputter.cpython-39.pyc b/dist/ba_data/python/efro/dataclassio/__pycache__/_inputter.cpython-39.pyc new file mode 100644 index 0000000..0f8aec5 Binary files /dev/null and b/dist/ba_data/python/efro/dataclassio/__pycache__/_inputter.cpython-39.pyc differ diff --git a/dist/ba_data/python/efro/dataclassio/__pycache__/_outputter.cpython-38.opt-1.pyc b/dist/ba_data/python/efro/dataclassio/__pycache__/_outputter.cpython-38.opt-1.pyc new file mode 100644 index 0000000..50505ef Binary files /dev/null and b/dist/ba_data/python/efro/dataclassio/__pycache__/_outputter.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python/efro/dataclassio/__pycache__/_outputter.cpython-38.pyc b/dist/ba_data/python/efro/dataclassio/__pycache__/_outputter.cpython-38.pyc new file mode 100644 index 0000000..a06574f Binary files /dev/null and b/dist/ba_data/python/efro/dataclassio/__pycache__/_outputter.cpython-38.pyc differ diff --git a/dist/ba_data/python/efro/dataclassio/__pycache__/_outputter.cpython-39.opt-1.pyc b/dist/ba_data/python/efro/dataclassio/__pycache__/_outputter.cpython-39.opt-1.pyc new file mode 100644 index 0000000..9a56c13 Binary files /dev/null and b/dist/ba_data/python/efro/dataclassio/__pycache__/_outputter.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/efro/dataclassio/__pycache__/_outputter.cpython-39.pyc b/dist/ba_data/python/efro/dataclassio/__pycache__/_outputter.cpython-39.pyc new file mode 100644 index 0000000..7a947ee Binary files /dev/null and b/dist/ba_data/python/efro/dataclassio/__pycache__/_outputter.cpython-39.pyc differ diff --git a/dist/ba_data/python/efro/dataclassio/__pycache__/_pathcapture.cpython-38.opt-1.pyc b/dist/ba_data/python/efro/dataclassio/__pycache__/_pathcapture.cpython-38.opt-1.pyc new file mode 100644 index 0000000..deb2494 Binary files /dev/null and b/dist/ba_data/python/efro/dataclassio/__pycache__/_pathcapture.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python/efro/dataclassio/__pycache__/_pathcapture.cpython-38.pyc b/dist/ba_data/python/efro/dataclassio/__pycache__/_pathcapture.cpython-38.pyc new file mode 100644 index 0000000..06a145a Binary files /dev/null and b/dist/ba_data/python/efro/dataclassio/__pycache__/_pathcapture.cpython-38.pyc differ diff --git a/dist/ba_data/python/efro/dataclassio/__pycache__/_pathcapture.cpython-39.opt-1.pyc b/dist/ba_data/python/efro/dataclassio/__pycache__/_pathcapture.cpython-39.opt-1.pyc new file mode 100644 index 0000000..148d36e Binary files /dev/null and b/dist/ba_data/python/efro/dataclassio/__pycache__/_pathcapture.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/efro/dataclassio/__pycache__/_pathcapture.cpython-39.pyc b/dist/ba_data/python/efro/dataclassio/__pycache__/_pathcapture.cpython-39.pyc new file mode 100644 index 0000000..ea82eb8 Binary files /dev/null and b/dist/ba_data/python/efro/dataclassio/__pycache__/_pathcapture.cpython-39.pyc differ diff --git a/dist/ba_data/python/efro/dataclassio/__pycache__/_prep.cpython-38.opt-1.pyc b/dist/ba_data/python/efro/dataclassio/__pycache__/_prep.cpython-38.opt-1.pyc new file mode 100644 index 0000000..f6f278f Binary files /dev/null and b/dist/ba_data/python/efro/dataclassio/__pycache__/_prep.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python/efro/dataclassio/__pycache__/_prep.cpython-38.pyc b/dist/ba_data/python/efro/dataclassio/__pycache__/_prep.cpython-38.pyc new file mode 100644 index 0000000..536aaf1 Binary files /dev/null and b/dist/ba_data/python/efro/dataclassio/__pycache__/_prep.cpython-38.pyc differ diff --git a/dist/ba_data/python/efro/dataclassio/__pycache__/_prep.cpython-39.opt-1.pyc b/dist/ba_data/python/efro/dataclassio/__pycache__/_prep.cpython-39.opt-1.pyc new file mode 100644 index 0000000..f68f5f8 Binary files /dev/null and b/dist/ba_data/python/efro/dataclassio/__pycache__/_prep.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/efro/dataclassio/__pycache__/_prep.cpython-39.pyc b/dist/ba_data/python/efro/dataclassio/__pycache__/_prep.cpython-39.pyc new file mode 100644 index 0000000..58851ad Binary files /dev/null and b/dist/ba_data/python/efro/dataclassio/__pycache__/_prep.cpython-39.pyc differ diff --git a/dist/ba_data/python/efro/dataclassio/__pycache__/extras.cpython-38.opt-1.pyc b/dist/ba_data/python/efro/dataclassio/__pycache__/extras.cpython-38.opt-1.pyc new file mode 100644 index 0000000..c766b69 Binary files /dev/null and b/dist/ba_data/python/efro/dataclassio/__pycache__/extras.cpython-38.opt-1.pyc differ diff --git a/dist/ba_data/python/efro/dataclassio/__pycache__/extras.cpython-39.opt-1.pyc b/dist/ba_data/python/efro/dataclassio/__pycache__/extras.cpython-39.opt-1.pyc new file mode 100644 index 0000000..1973c2d Binary files /dev/null and b/dist/ba_data/python/efro/dataclassio/__pycache__/extras.cpython-39.opt-1.pyc differ diff --git a/dist/ba_data/python/efro/dataclassio/_base.py b/dist/ba_data/python/efro/dataclassio/_base.py new file mode 100644 index 0000000..492a8d4 --- /dev/null +++ b/dist/ba_data/python/efro/dataclassio/_base.py @@ -0,0 +1,186 @@ +# Released under the MIT License. See LICENSE for details. +# +"""Core components of dataclassio.""" + +from __future__ import annotations + +import dataclasses +import typing +import datetime +from enum import Enum +from typing import TYPE_CHECKING, get_args +# noinspection PyProtectedMember +from typing import _AnnotatedAlias # type: ignore + +_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 + +if TYPE_CHECKING: + from typing import Any, Optional + +# Types which we can pass through as-is. +SIMPLE_TYPES = {int, bool, str, float, type(None)} + +# Attr name for dict of extra attributes included on dataclass instances. +# Note that this is only added if extra attributes are present. +EXTRA_ATTRS_ATTR = '_DCIOEXATTRS' + + +def _ensure_datetime_is_timezone_aware(value: datetime.datetime) -> None: + # We only support timezone-aware utc times. + if (value.tzinfo is not datetime.timezone.utc + and (_pytz_utc is None or value.tzinfo is not _pytz_utc)): + raise ValueError( + 'datetime values must have timezone set as timezone.utc') + + +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) + if len(expected) == 1: + expected_str = expected[0].__name__ + else: + names = ', '.join(t.__name__ for t in expected) + expected_str = f'Union[{names}]' + raise TypeError(f'Invalid value type for "{fieldpath}";' + f' expected "{expected_str}", got' + f' "{valuetype.__name__}".') + + +class Codec(Enum): + """Specifies expected data format exported to or imported from.""" + + # Use only types that will translate cleanly to/from json: lists, + # dicts with str keys, bools, ints, floats, and None. + JSON = 'json' + + # Mostly like JSON but passes bytes and datetime objects through + # as-is instead of converting them to json-friendly types. + FIRESTORE = 'firestore' + + +def _is_valid_for_codec(obj: Any, codec: Codec) -> bool: + """Return whether a value consists solely of json-supported types. + + Note that this does not include things like tuples which are + implicitly translated to lists by python's json module. + """ + if obj is None: + return True + + objtype = type(obj) + if objtype in (int, float, str, bool): + return True + if objtype is dict: + # 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()) + if objtype is list: + return all(_is_valid_for_codec(elem, codec) for elem in obj) + + # A few things are valid in firestore but not json. + if issubclass(objtype, datetime.datetime) or objtype is bytes: + return codec is Codec.FIRESTORE + + return False + + +class IOAttrs: + """For specifying io behavior in annotations.""" + + storagename: Optional[str] = None + store_default: bool = True + whole_days: bool = False + whole_hours: bool = False + + def __init__(self, + storagename: Optional[str] = storagename, + store_default: bool = store_default, + whole_days: bool = whole_days, + whole_hours: bool = whole_hours): + + # Only store values that differ from class defaults to keep + # our instances nice and lean. + cls = type(self) + if storagename != cls.storagename: + self.storagename = storagename + if store_default != cls.store_default: + self.store_default = store_default + if whole_days != cls.whole_days: + self.whole_days = whole_days + if whole_hours != cls.whole_hours: + self.whole_hours = whole_hours + + def validate_for_field(self, cls: type, field: dataclasses.Field) -> None: + """Ensure the IOAttrs instance is ok to use with the provided field.""" + + # Turning off store_default requires the field to have either + # a default_factory or a default + if not self.store_default: + default_factory: Any = field.default_factory # type: ignore + if (default_factory is dataclasses.MISSING + and field.default is dataclasses.MISSING): + raise TypeError(f'Field {field.name} of {cls} has' + f' neither a default nor a default_factory;' + f' store_default=False cannot be set for it.') + + 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)): + raise ValueError( + 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.') + + +def _get_origin(anntype: Any) -> Any: + """Given a type annotation, return its origin or itself if there is none. + + This differs from typing.get_origin in that it will never return None. + This lets us use the same code path for handling typing.List + that we do for handling list, which is good since they can be used + interchangeably in annotations. + """ + origin = typing.get_origin(anntype) + return anntype if origin is None else origin + + +def _parse_annotated(anntype: Any) -> tuple[Any, Optional[IOAttrs]]: + """Parse Annotated() constructs, returning annotated type & IOAttrs.""" + # If we get an Annotated[foo, bar, eep] we take + # foo as the actual type and we look for IOAttrs instances in + # bar/eep to affect our behavior. + ioattrs: Optional[IOAttrs] = None + if isinstance(anntype, _AnnotatedAlias): + annargs = get_args(anntype) + for annarg in annargs[1:]: + if isinstance(annarg, IOAttrs): + if ioattrs is not None: + raise RuntimeError( + 'Multiple IOAttrs instances found for a' + ' single annotation; this is not supported.') + ioattrs = annarg + + # I occasionally just throw a 'x' down when I mean IOAttrs('x'); + # catch these mistakes. + 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.') + 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 new file mode 100644 index 0000000..911fe4f --- /dev/null +++ b/dist/ba_data/python/efro/dataclassio/_inputter.py @@ -0,0 +1,403 @@ +# Released under the MIT License. See LICENSE for details. +# +"""Functionality for dataclassio related to pulling data into dataclasses.""" + +# Note: We do lots of comparing of exact types here which is normally +# frowned upon (stuff like isinstance() is usually encouraged). +# pylint: disable=unidiomatic-typecheck + +from __future__ import annotations + +from enum import Enum +import dataclasses +import typing +import datetime +from typing import TYPE_CHECKING, Generic, TypeVar + +from efro.util import enum_by_value +from efro.dataclassio._base import (Codec, _parse_annotated, EXTRA_ATTRS_ATTR, + _is_valid_for_codec, _get_origin, + SIMPLE_TYPES, _raise_type_error, + _ensure_datetime_is_timezone_aware) +from efro.dataclassio._prep import PrepSession + +if TYPE_CHECKING: + from typing import Any, Optional + from efro.dataclassio._base import IOAttrs + +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): + self._cls = cls + self._codec = codec + self._coerce_to_float = coerce_to_float + self._allow_unknown_attrs = allow_unknown_attrs + self._discard_unknown_attrs = discard_unknown_attrs + + if not allow_unknown_attrs and discard_unknown_attrs: + raise ValueError('discard_unknown_attrs cannot be True' + ' when allow_unknown_attrs is False.') + + def run(self, values: dict) -> T: + """Do the thing.""" + out = self._dataclass_from_input(self._cls, '', values) + assert isinstance(out, self._cls) + return out + + def _value_from_input(self, cls: type, fieldpath: str, anntype: Any, + value: Any, ioattrs: Optional[IOAttrs]) -> Any: + """Convert an assigned value to what a dataclass field expects.""" + # pylint: disable=too-many-return-statements + # pylint: disable=too-many-branches + + origin = _get_origin(anntype) + + 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.') + return value + + if origin is typing.Union: + # Currently the only unions we support are None/Value + # (translated from Optional), which we verified on prep. + # So let's treat this as a simple optional case. + if value is None: + return None + childanntypes_l = [ + c for c in typing.get_args(anntype) if c is not type(None) + ] + assert len(childanntypes_l) == 1 + 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). + assert isinstance(origin, type) + + 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): + return float(value) + _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) + + if origin is tuple: + return self._tuple_from_input(cls, fieldpath, anntype, value, + ioattrs) + + if origin is dict: + return self._dict_from_input(cls, fieldpath, anntype, value, + ioattrs) + + if dataclasses.is_dataclass(origin): + return self._dataclass_from_input(origin, fieldpath, value) + + if issubclass(origin, Enum): + return enum_by_value(origin, value) + + if issubclass(origin, datetime.datetime): + return self._datetime_from_input(cls, fieldpath, value, ioattrs) + + if origin is bytes: + return self._bytes_from_input(origin, fieldpath, value) + + raise TypeError( + f"Field '{fieldpath}' of type '{anntype}' is unsupported here.") + + def _bytes_from_input(self, cls: type, fieldpath: str, + value: Any) -> bytes: + """Given input data, returns bytes.""" + import base64 + + # For firestore, bytes are passed as-is. Otherwise they're encoded + # 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)}.') + + 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)}.') + return base64.b64decode(value) + + 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 + dataclass_to_dict. This means that sequence values such as tuples or + sets should be passed as lists, enums should be passed as their + associated values, and nested dataclasses should be passed as dicts. + """ + # pylint: disable=too-many-locals + if not isinstance(values, dict): + raise TypeError( + f'Expected a dict for {fieldpath} on {cls.__name__};' + f' got a {type(values)}.') + + prep = PrepSession(explicit=False).prep_dataclass(cls, + recursion_level=0) + + extra_attrs = {} + + # noinspection PyDataclass + fields = dataclasses.fields(cls) + fields_by_name = {f.name: f for f in fields} + args: dict[str, Any] = {} + for rawkey, value in values.items(): + key = prep.storage_names_to_attr_names.get(rawkey, rawkey) + field = fields_by_name.get(key) + + # Store unknown attrs off to the side (or error if desired). + if field is None: + if self._allow_unknown_attrs: + if self._discard_unknown_attrs: + continue + + # Treat this like 'Any' data; ensure that it is valid + # raw json. + if not _is_valid_for_codec(value, self._codec): + raise TypeError( + f'Unknown attr \'{key}\'' + f' on {fieldpath} contains data type(s)' + f' not supported by the specified codec' + f' ({self._codec.name}).') + extra_attrs[key] = value + else: + raise AttributeError( + f"'{cls.__name__}' has no '{key}' field.") + else: + fieldname = field.name + anntype = prep.annotations[fieldname] + anntype, ioattrs = _parse_annotated(anntype) + + subfieldpath = (f'{fieldpath}.{fieldname}' + if fieldpath else fieldname) + args[key] = self._value_from_input(cls, subfieldpath, anntype, + value, ioattrs) + try: + out = cls(**args) + except Exception as exc: + raise RuntimeError(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 _dict_from_input(self, cls: type, fieldpath: str, anntype: Any, + value: Any, ioattrs: Optional[IOAttrs]) -> 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)}.') + + childtypes = typing.get_args(anntype) + assert len(childtypes) in (0, 2) + + out: dict + + # 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}).') + out = value + else: + out = {} + keyanntype, valanntype = childtypes + + # Ok; we've got definite key/value types (which we verified as + # valid during prep). Run all keys/values through it. + + # str keys we just take directly since that's supported by json. + if keyanntype is str: + for key, val in value.items(): + if not isinstance(key, str): + 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) + + # int keys are stored in json as str versions of themselves. + elif keyanntype is int: + for key, val in value.items(): + if not isinstance(key, str): + raise TypeError( + f'Got invalid key type {type(key)} for' + f' dict key at \'{fieldpath}\' on {cls.__name__};' + 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 + out[keyint] = self._value_from_input( + cls, fieldpath, valanntype, val, ioattrs) + + elif issubclass(keyanntype, Enum): + # In prep we verified that all these enums' values have + # the same type, so we can just look at the first to see if + # this is a string enum or an int enum. + enumvaltype = type(next(iter(keyanntype)).value) + assert enumvaltype in (int, str) + if enumvaltype is str: + for key, val in value.items(): + try: + enumval = enum_by_value(keyanntype, key) + except ValueError as exc: + raise ValueError( + f'Got invalid key value {key} for' + f' dict key at \'{fieldpath}\'' + f' on {cls.__name__};' + f' expected a value corresponding to' + f' a {keyanntype}.') from exc + out[enumval] = self._value_from_input( + cls, fieldpath, valanntype, val, ioattrs) + else: + for key, val in value.items(): + try: + enumval = enum_by_value(keyanntype, int(key)) + except (ValueError, TypeError) as exc: + raise ValueError( + f'Got invalid key value {key} for' + f' dict key at \'{fieldpath}\'' + f' on {cls.__name__};' + f' expected {keyanntype} value (though' + f' in string form).') from exc + out[enumval] = self._value_from_input( + 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: Optional[IOAttrs]) -> 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__}') + + childanntypes = typing.get_args(anntype) + + # 'Any' type children; make sure they are valid json values + # and then just grab them. + 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.') + return value if type(value) is seqtype else seqtype(value) + + # We contain elements of some specified type. + assert len(childanntypes) == 1 + childanntype = childanntypes[0] + return seqtype( + self._value_from_input(cls, fieldpath, childanntype, i, ioattrs) + for i in value) + + def _datetime_from_input(self, cls: type, fieldpath: str, value: Any, + ioattrs: Optional[IOAttrs]) -> Any: + + # For firestore we expect a datetime object. + if self._codec is Codec.FIRESTORE: + # Don't compare exact type here, as firestore can give us + # a subclass with extended precision. + if not isinstance(value, datetime.datetime): + raise TypeError( + f'Invalid input value for "{fieldpath}" on' + f' "{cls.__name__}";' + f' expected a datetime, got a {type(value).__name__}') + _ensure_datetime_is_timezone_aware(value) + return value + + assert self._codec is Codec.JSON + + # We expect a list of 7 ints. + 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__}') + if len(value) != 7 or not all(isinstance(x, int) for x in value): + raise TypeError( + f'Invalid input value for "{fieldpath}" on "{cls.__name__}";' + f' expected a list of 7 ints.') + out = datetime.datetime( # type: ignore + *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: Optional[IOAttrs]) -> 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__}') + + childanntypes = typing.get_args(anntype) + + # We should have verified this to be non-zero at prep-time. + assert childanntypes + + if len(value) != len(childanntypes): + raise TypeError(f'Invalid tuple input for "{fieldpath}";' + f' expected {len(childanntypes)} values,' + f' found {len(value)}.') + + for i, childanntype in enumerate(childanntypes): + childval = value[i] + + # 'Any' type children; make sure they are valid json values + # 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.') + out.append(childval) + else: + out.append( + 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 new file mode 100644 index 0000000..9dc193e --- /dev/null +++ b/dist/ba_data/python/efro/dataclassio/_outputter.py @@ -0,0 +1,348 @@ +# Released under the MIT License. See LICENSE for details. +# +"""Functionality for dataclassio related to exporting data from dataclasses.""" + +# Note: We do lots of comparing of exact types here which is normally +# frowned upon (stuff like isinstance() is usually encouraged). +# pylint: disable=unidiomatic-typecheck + +from __future__ import annotations + +from enum import Enum +import dataclasses +import typing +import datetime +from typing import TYPE_CHECKING + +from efro.dataclassio._base import (Codec, _parse_annotated, EXTRA_ATTRS_ATTR, + _is_valid_for_codec, _get_origin, + SIMPLE_TYPES, _raise_type_error, + _ensure_datetime_is_timezone_aware) +from efro.dataclassio._prep import PrepSession + +if TYPE_CHECKING: + from typing import Any, Optional + from efro.dataclassio._base import IOAttrs + + +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: + self._obj = obj + self._create = create + self._codec = codec + self._coerce_to_float = coerce_to_float + + def run(self) -> Any: + """Do the thing.""" + return self._process_dataclass(type(self._obj), self._obj, '') + + 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) + fields = dataclasses.fields(obj) + out: Optional[dict[str, Any]] = {} if self._create else None + for field in fields: + fieldname = field.name + if fieldpath: + subfieldpath = f'{fieldpath}.{fieldname}' + else: + subfieldpath = fieldname + anntype = prep.annotations[fieldname] + value = getattr(obj, fieldname) + + anntype, ioattrs = _parse_annotated(anntype) + + # If we're not storing default values for this fella, + # we can skip all output processing if we've got a default value. + if ioattrs is not None and not ioattrs.store_default: + default_factory: Any = field.default_factory # type: ignore + if default_factory is not dataclasses.MISSING: + if default_factory() == value: + continue + elif field.default is not dataclasses.MISSING: + if field.default == value: + continue + else: + raise RuntimeError( + f'Field {fieldname} of {cls.__name__} has' + f' neither a default nor a default_factory;' + f' store_default=False cannot be set for it.' + f' (AND THIS SHOULD HAVE BEEN CAUGHT IN PREP!)') + + 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) + out[storagename] = outvalue + + # If there's extra-attrs stored on us, check/include them. + extra_attrs = getattr(obj, EXTRA_ATTRS_ATTR, None) + if isinstance(extra_attrs, dict): + if not _is_valid_for_codec(extra_attrs, self._codec): + raise TypeError( + f'Extra attrs on {fieldpath} contains data type(s)' + f' not supported by json.') + 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: Optional[IOAttrs]) -> Any: + # pylint: disable=too-many-return-statements + # pylint: disable=too-many-branches + # pylint: disable=too-many-statements + + origin = _get_origin(anntype) + + 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 types directly" + f' supported by the specified codec ({self._codec.name});' + f' found \'{type(value).__name__}\' which is not.') + return value if self._create else None + + if origin is typing.Union: + # Currently the only unions we support are None/Value + # (translated from Optional), which we verified on prep. + # So let's treat this as a simple optional case. + if value is None: + return None + childanntypes_l = [ + c for c in typing.get_args(anntype) if c is not type(None) + ] + assert len(childanntypes_l) == 1 + 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). + assert isinstance(origin, type) + + # For simple flat types, look for exact matches: + 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): + return float(value) if self._create else None + _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)}') + 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)}.') + if self._create: + return [ + 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) + return None + + if origin is list: + if not isinstance(value, list): + 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 + # the specified codec. + 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 the specified' + f' codec ({self._codec.name}).') + # Hmm; should we do a copy here? + return value if self._create else None + + # We contain elements of some specified type. + assert len(childanntypes) == 1 + if self._create: + return [ + 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) + return None + + if origin is set: + if not isinstance(value, set): + 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. + if len(childanntypes) == 0 or childanntypes[0] is typing.Any: + for child in value: + if not _is_valid_for_codec(child, self._codec): + raise TypeError( + f'Set at {fieldpath} contains' + f' data type(s) not supported by the' + f' specified codec ({self._codec.name}).') + return list(value) if self._create else None + + # We contain elements of some specified type. + assert len(childanntypes) == 1 + if self._create: + # 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 + ] + for x in value: + self._process_value(cls, fieldpath, childanntypes[0], x, + ioattrs) + return None + + if origin is dict: + return self._process_dict(cls, fieldpath, anntype, value, ioattrs) + + if dataclasses.is_dataclass(origin): + if not isinstance(value, origin): + 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)}.') + # 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)}.') + _ensure_datetime_is_timezone_aware(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 + + if origin is bytes: + return self._process_bytes(cls, fieldpath, value) + + raise TypeError( + 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)}.') + + if not self._create: + return None + + # In JSON we convert to base64, but firestore directly supports bytes. + if self._codec is Codec.JSON: + return base64.b64encode(value).decode() + + assert self._codec is Codec.FIRESTORE + return value + + def _process_dict(self, cls: type, fieldpath: str, anntype: Any, + value: dict, ioattrs: Optional[IOAttrs]) -> Any: + # pylint: disable=too-many-branches + if not isinstance(value, dict): + 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): + 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.') + return value if self._create else None + + # Ok; we've got a definite key type (which we verified as valid + # during prep). Make sure all keys match it. + out: Optional[dict] = {} if self._create else None + keyanntype, valanntype = childtypes + + # str keys we just export directly since that's supported by json. + if keyanntype is str: + for key, val in value.items(): + if not isinstance(key, str): + 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) + if self._create: + assert out is not None + out[key] = outval + + # int keys are stored as str versions of themselves. + elif keyanntype is int: + for key, val in value.items(): + if not isinstance(key, int): + 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) + if self._create: + assert out is not None + out[str(key)] = outval + + elif issubclass(keyanntype, Enum): + for key, val in value.items(): + if not isinstance(key, keyanntype): + 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) + if self._create: + assert out is not None + out[str(key.value)] = outval + else: + raise RuntimeError(f'Unhandled dict out-key-type {keyanntype}') + + return out diff --git a/dist/ba_data/python/efro/dataclassio/_pathcapture.py b/dist/ba_data/python/efro/dataclassio/_pathcapture.py new file mode 100644 index 0000000..d325b6a --- /dev/null +++ b/dist/ba_data/python/efro/dataclassio/_pathcapture.py @@ -0,0 +1,106 @@ +# Released under the MIT License. See LICENSE for details. +# +"""Functionality related to capturing nested dataclass paths.""" + +from __future__ import annotations + +import dataclasses +from typing import TYPE_CHECKING, TypeVar, Generic + +from efro.dataclassio._base import _parse_annotated, _get_origin +from efro.dataclassio._prep import PrepSession + +if TYPE_CHECKING: + from typing import Any, Callable + +T = TypeVar('T') + + +class _PathCapture: + """Utility for obtaining dataclass storage paths in a type safe way.""" + + def __init__(self, obj: Any, pathparts: list[str] = None): + self._is_dataclass = dataclasses.is_dataclass(obj) + if pathparts is None: + pathparts = [] + self._cls = obj if isinstance(obj, type) else type(obj) + self._pathparts = pathparts + + def __getattr__(self, name: str) -> _PathCapture: + + # We only allow diving into sub-objects if we are a dataclass. + if not self._is_dataclass: + raise TypeError( + f"Field path cannot include attribute '{name}' " + f'under parent {self._cls}; parent types must be dataclasses.') + + prep = PrepSession(explicit=False).prep_dataclass(self._cls, + recursion_level=0) + 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) + origin = _get_origin(anntype) + return _PathCapture(origin, pathparts=self._pathparts + [storagename]) + + @property + def path(self) -> str: + """The final output path.""" + return '.'.join(self._pathparts) + + +class DataclassFieldLookup(Generic[T]): + """Get info about nested dataclass fields in type-safe way.""" + + def __init__(self, cls: type[T]) -> None: + self.cls = cls + + def path(self, callback: Callable[[T], Any]) -> str: + """Look up a path on child dataclass fields. + + example: + DataclassFieldLookup(MyType).path(lambda obj: obj.foo.bar) + + The above example will return the string 'foo.bar' or something + like 'f.b' if the dataclasses have custom storage names set. + It will also be static-type-checked, triggering an error if + MyType.foo.bar is not a valid path. Note, however, that the + callback technically allows any return value but only nested + dataclasses and their fields will succeed. + """ + + # We tell the type system that we are returning an instance + # of our class, which allows it to perform type checking on + # member lookups. In reality, however, we are providing a + # special object which captures path lookups so we can build + # a string from them. + 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)}.') + return out.path + return '' + + def paths(self, callback: Callable[[T], list[Any]]) -> list[str]: + """Look up multiple paths on child dataclass fields. + + Functionality is identical to path() but for multiple paths at once. + + example: + DataclassFieldLookup(MyType).paths(lambda obj: [obj.foo, obj.bar]) + """ + outvals: list[str] = [] + if not TYPE_CHECKING: + outs = callback(_PathCapture(self.cls)) + assert isinstance(outs, list) + for out in outs: + if not isinstance(out, _PathCapture): + raise TypeError( + f'Expected a valid path under' + 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 new file mode 100644 index 0000000..55cc814 --- /dev/null +++ b/dist/ba_data/python/efro/dataclassio/_prep.py @@ -0,0 +1,335 @@ +# Released under the MIT License. See LICENSE for details. +# +"""Functionality for prepping types for use with dataclassio.""" + +# Note: We do lots of comparing of exact types here which is normally +# frowned upon (stuff like isinstance() is usually encouraged). +# pylint: disable=unidiomatic-typecheck + +from __future__ import annotations + +import logging +from enum import Enum +import dataclasses +import typing +import datetime +from typing import TYPE_CHECKING, TypeVar, get_type_hints + +# noinspection PyProtectedMember +from efro.dataclassio._base import _parse_annotated, _get_origin, SIMPLE_TYPES + +if TYPE_CHECKING: + from typing import Any + +T = TypeVar('T') + +# How deep we go when prepping nested types +# (basically for detecting recursive types) +MAX_RECURSION = 10 + +# Attr name for data we store on dataclass types as part of prep. +PREP_ATTR = '_DCIOPREP' + + +def ioprep(cls: type) -> None: + """Prep a dataclass type for use with this module's functionality. + + Prepping ensures that all types contained in a data class as well as + the usage of said types are supported by this module and pre-builds + necessary constructs needed for encoding/decoding/etc. + + Prepping will happen on-the-fly as needed, but a warning will be + emitted in such cases, as it is better to explicitly prep all used types + early in a process to ensure any invalid types or configuration are caught + immediately. + + Prepping a dataclass involves evaluating its type annotations, which, + as of PEP 563, are stored simply as strings. This evaluation is done + in the module namespace containing the class, so all referenced types + must be defined at that level. + """ + PrepSession(explicit=True).prep_dataclass(cls, recursion_level=0) + + +def ioprepped(cls: type[T]) -> type[T]: + """Class decorator for easily prepping a dataclass at definition time. + + Note that in some cases it may not be possible to prep a dataclass + immediately (such as when its type annotations refer to forward-declared + types). In these cases, dataclass_prep() should be explicitly called for + the class as soon as possible; ideally at module import time to expose any + errors as early as possible in execution. + """ + ioprep(cls) + return cls + + +def is_ioprepped_dataclass(obj: Any) -> bool: + """Return whether the obj is an ioprepped dataclass type or instance.""" + cls = obj if isinstance(obj, type) else type(obj) + return dataclasses.is_dataclass(cls) and hasattr(cls, PREP_ATTR) + + +@dataclasses.dataclass +class PrepData: + """Data we prepare and cache for a class during prep. + + This data is used as part of the encoding/decoding/validating process. + """ + + # Resolved annotation data with 'live' classes. + annotations: dict[str, Any] + + # Map of storage names to attr names. + storage_names_to_attr_names: dict[str, str] + + +class PrepSession: + """Context for a prep.""" + + def __init__(self, explicit: bool): + self.explicit = explicit + + def prep_dataclass(self, cls: type, recursion_level: int) -> PrepData: + """Run prep on a dataclass if necessary and return its prep data.""" + + # We should only need to do this once per dataclass. + existing_data = getattr(cls, PREP_ATTR, None) + if existing_data is not None: + assert isinstance(existing_data, PrepData) + return existing_data + + # If we run into classes containing themselves, we may have + # to do something smarter to handle it. + if recursion_level > MAX_RECURSION: + raise RuntimeError('Max recursion exceeded.') + + # We should only be passed classes which are dataclasses. + if not isinstance(cls, type) or not dataclasses.is_dataclass(cls): + raise TypeError(f'Passed arg {cls} is not a dataclass type.') + + # Generate a warning on non-explicit preps; we prefer prep to + # happen explicitly at runtime so errors can be detected early on. + if not self.explicit: + logging.warning( + 'efro.dataclassio: implicitly prepping dataclass: %s.' + ' 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) + + 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), + include_extras=True) + # pylint: enable=unexpected-keyword-arg + except Exception as exc: + print('GOT', cls.__dict__) + 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 + + # noinspection PyDataclass + fields = dataclasses.fields(cls) + fields_by_name = {f.name: f for f in fields} + + all_storage_names: set[str] = set() + storage_names_to_attr_names: dict[str, str] = {} + + # Ok; we've resolved actual types for this dataclass. + # now recurse through them, verifying that we support all contained + # types and prepping any contained dataclass types. + for attrname, anntype in resolved_annotations.items(): + + anntype, ioattrs = _parse_annotated(anntype) + + # If we found attached IOAttrs data, make sure it contains + # valid values for the field it is attached to. + if ioattrs is not None: + ioattrs.validate_for_field(cls, fields_by_name[attrname]) + if ioattrs.storagename is not None: + storagename = ioattrs.storagename + storage_names_to_attr_names[ioattrs.storagename] = attrname + else: + storagename = attrname + else: + storagename = attrname + + # 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}\'') + all_storage_names.add(storagename) + + self.prep_type(cls, + attrname, + anntype, + 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) + setattr(cls, PREP_ATTR, prepdata) + return prepdata + + def prep_type(self, cls: type, attrname: str, anntype: Any, + recursion_level: int) -> None: + """Run prep on a dataclass.""" + # pylint: disable=too-many-return-statements + # pylint: disable=too-many-branches + + # If we run into classes containing themselves, we may have + # to do something smarter to handle it. + if recursion_level > MAX_RECURSION: + raise RuntimeError('Max recursion exceeded.') + + origin = _get_origin(anntype) + + if origin is typing.Union: + self.prep_union(cls, + attrname, + anntype, + recursion_level=recursion_level + 1) + return + + if anntype is typing.Any: + return + + # Everything below this point assumes the annotation type resolves + # to a concrete type. + if not isinstance(origin, type): + raise TypeError( + f'Unsupported type found for \'{attrname}\' on {cls}:' + f' {anntype}') + + if origin in SIMPLE_TYPES: + return + + # For sets and lists, check out their single contained type (if any). + if origin in (list, set): + childtypes = typing.get_args(anntype) + if len(childtypes) == 0: + # This is equivalent to Any; nothing else needs checking. + return + 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], + recursion_level=recursion_level + 1) + return + + if origin is dict: + childtypes = typing.get_args(anntype) + assert len(childtypes) in (0, 2) + + # For key types we support Any, str, int, + # and Enums with uniform str/int values. + if not childtypes or childtypes[0] is typing.Any: + # 'Any' needs no further checks (just checked per-instance). + pass + elif childtypes[0] in (str, int): + # str and int are all good as keys. + pass + elif issubclass(childtypes[0], Enum): + # Allow our usual str or int enum types as keys. + self.prep_enum(childtypes[0]) + else: + raise TypeError( + f'Dict key type {childtypes[0]} for \'{attrname}\'' + 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], + recursion_level=recursion_level + 1) + return + + # For Tuples, simply check individual member types. + # (and, for now, explicitly disallow zero member types or usage + # of ellipsis) + if origin is tuple: + childtypes = typing.get_args(anntype) + if not childtypes: + raise TypeError( + f'Tuple at \'{attrname}\'' + 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.') + for childtype in childtypes: + self.prep_type(cls, + attrname, + childtype, + recursion_level=recursion_level + 1) + return + + if issubclass(origin, Enum): + self.prep_enum(origin) + return + + # We allow datetime objects (and google's extended subclass of them + # used in firestore, which is why we don't look for exact type here). + if issubclass(origin, datetime.datetime): + return + + if dataclasses.is_dataclass(origin): + self.prep_dataclass(origin, recursion_level=recursion_level + 1) + return + + if origin is bytes: + return + + 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: + """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): + 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, + recursion_level=recursion_level + 1) + + def prep_enum(self, enumtype: type[Enum]) -> None: + """Run prep on an enum type.""" + + valtype: Any = None + + # We currently support enums with str or int values; fail if we + # 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.') + 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.') diff --git a/dist/ba_data/python/efro/dataclassio/extras.py b/dist/ba_data/python/efro/dataclassio/extras.py new file mode 100644 index 0000000..ed63939 --- /dev/null +++ b/dist/ba_data/python/efro/dataclassio/extras.py @@ -0,0 +1,66 @@ +# Released under the MIT License. See LICENSE for details. +# +"""Extra rarely-needed functionality related to dataclasses.""" + +from __future__ import annotations + +import dataclasses +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any, Optional + + +def dataclass_diff(obj1: Any, obj2: Any) -> str: + """Generate a string showing differences between two dataclass instances. + + Both must be of the exact same type. + """ + diff = _diff(obj1, obj2, 2) + return ' ' if diff == '' else diff + + +class DataclassDiff: + """Wraps dataclass_diff() in an object for efficiency. + + It is preferable to pass this to logging calls instead of the + final diff string since the diff will never be generated if + the associated logging level is not being emitted. + """ + + def __init__(self, obj1: Any, obj2: Any): + self._obj1 = obj1 + self._obj2 = obj2 + + def __repr__(self) -> str: + return dataclass_diff(self._obj1, self._obj2) + + +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)}).') + bits: list[str] = [] + indentstr = ' ' * indent + fields = dataclasses.fields(obj1) + for field in fields: + fieldname = field.name + val1 = getattr(obj1, fieldname) + 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)): + diff = _diff(val1, val2, indent + 2) + if diff != '': + bits.append(f'{indentstr}{fieldname}:') + bits.append(diff) + + # For all else just do a single line + # (perhaps we could improve on this for other complex types) + else: + if val1 != val2: + bits.append(f'{indentstr}{fieldname}: {val1} -> {val2}') + return '\n'.join(bits) diff --git a/dist/ba_data/python/efro/error.py b/dist/ba_data/python/efro/error.py index 80bd3ce..4627ffc 100644 --- a/dist/ba_data/python/efro/error.py +++ b/dist/ba_data/python/efro/error.py @@ -1,6 +1,6 @@ # Released under the MIT License. See LICENSE for details. # -"""Functionality for dealing with errors.""" +"""Common errors and related functionality.""" from __future__ import annotations from typing import TYPE_CHECKING @@ -33,3 +33,99 @@ class CleanError(Exception): errstr = str(self) if errstr: print(f'{Clr.SRED}{errstr}{Clr.RST}', flush=flush) + + +class CommunicationError(Exception): + """A communication related error has occurred. + + This covers anything network-related going wrong in the sending + of data or receiving of a response. This error does not imply + that data was not received on the other end; only that a full + response round trip was not completed. + + These errors should be gracefully handled whenever possible, as + occasional network outages are generally unavoidable. + """ + + +class RemoteError(Exception): + """An error occurred on the other end of some connection. + + This occurs when communication succeeds but another type of error + occurs remotely. The error string can consist of a remote stack + trace or a simple message depending on the context. + + Depending on the situation, more specific error types such as CleanError + may be raised due to the remote error, so this one is considered somewhat + of a catch-all. + """ + + def __str__(self) -> str: + s = ''.join(str(arg) for arg in self.args) + return f'Remote Exception Follows:\n{s}' + + +def is_urllib_network_error(exc: BaseException) -> bool: + """Is the provided exception from urllib a network-related error? + + This should be passed an exception which resulted from opening or + reading a urllib Request. It returns True for any errors that could + conceivably arise due to unavailable/poor network connections, + firewall/connectivity issues, etc. These issues can often be safely + ignored or presented to the user as general 'network-unavailable' + states. + """ + import urllib.request + import urllib.error + import http.client + import errno + import socket + if isinstance( + exc, + (urllib.error.URLError, ConnectionError, http.client.IncompleteRead, + http.client.BadStatusLine, socket.timeout)): + return True + if isinstance(exc, OSError): + if exc.errno == 10051: # Windows unreachable network error. + return True + if exc.errno in { + errno.ETIMEDOUT, + errno.EHOSTUNREACH, + errno.ENETUNREACH, + }: + return True + return False + + +def is_udp_network_error(exc: BaseException) -> bool: + """Is the provided exception a network-related error? + + This should be passed an exception which resulted from creating and + using a socket.SOCK_DGRAM type socket. It should return True for any + errors that could conceivably arise due to unavailable/poor network + connections, firewall/connectivity issues, etc. These issues can often + be safely ignored or presented to the user as general + 'network-unavailable' states. + """ + import errno + if isinstance(exc, ConnectionRefusedError): + return True + if isinstance(exc, OSError): + 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, + }: + return True + return False diff --git a/dist/ba_data/python/efro/message.py b/dist/ba_data/python/efro/message.py new file mode 100644 index 0000000..8d9bbfc --- /dev/null +++ b/dist/ba_data/python/efro/message.py @@ -0,0 +1,991 @@ +# Released under the MIT License. See LICENSE for details. +# +"""Functionality for sending and responding to messages. +Supports static typing for message types and possible return types. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, TypeVar, Annotated +from dataclasses import dataclass +from enum import Enum +import inspect +import logging +import json +import traceback + +from efro.error import CleanError, RemoteError +from efro.dataclassio import (ioprepped, is_ioprepped_dataclass, IOAttrs, + dataclass_to_dict, dataclass_from_dict) + +if TYPE_CHECKING: + from typing import Any, Callable, Optional, Sequence, Union, Awaitable + +TM = TypeVar('TM', bound='MessageSender') + + +class Message: + """Base class for messages.""" + + @classmethod + def get_response_types(cls) -> list[type[Response]]: + """Return all message types this Message can result in when sent. + + The default implementation specifies EmptyResponse, so messages with + no particular response needs can leave this untouched. + Note that ErrorMessage is handled as a special case and does not + need to be specified here. + """ + return [EmptyResponse] + + +class Response: + """Base class for responses to messages.""" + + +# Some standard response types: + + +class ErrorType(Enum): + """Type of error that occurred in remote message handling.""" + OTHER = 0 + CLEAN = 1 + + +@ioprepped +@dataclass +class ErrorResponse(Response): + """Message saying some error has occurred on the other end. + + This type is unique in that it is not returned to the user; it + instead results in a local exception being raised. + """ + error_message: Annotated[str, IOAttrs('m')] + error_type: Annotated[ErrorType, IOAttrs('e')] = ErrorType.OTHER + + +@ioprepped +@dataclass +class EmptyResponse(Response): + """The response equivalent of None.""" + + +# TODO: could allow handlers to deal in raw values for these +# types similar to how we allow None in place of EmptyResponse. +# Though not sure if they are widely used enough to warrant the +# extra code complexity. +@ioprepped +@dataclass +class BoolResponse(Response): + """A simple bool value response.""" + + value: Annotated[bool, IOAttrs('v')] + + +@ioprepped +@dataclass +class StringResponse(Response): + """A simple string value response.""" + + value: Annotated[str, IOAttrs('v')] + + +class MessageProtocol: + """Wrangles a set of message types, formats, and response types. + Both endpoints must be using a compatible Protocol for communication + to succeed. To maintain Protocol compatibility between revisions, + all message types must retain the same id, message attr storage names must + not change, newly added attrs must have default values, etc. + """ + + def __init__(self, + message_types: dict[int, type[Message]], + response_types: dict[int, type[Response]], + type_key: Optional[str] = None, + preserve_clean_errors: bool = True, + log_remote_exceptions: bool = True, + trusted_sender: bool = False) -> 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 'type_key' is provided, the message type ID is stored as the + provided key in the message dict; otherwise it will be stored as + part of a top level dict with the message payload appearing as a + child dict. This is mainly for backwards compatibility. + + If 'preserve_clean_errors' is True, efro.error.CleanError errors + on the remote end will result in the same error raised locally. + All other Exception types come across as efro.error.RemoteError. + + If 'trusted_sender' is True, stringified remote stack traces will + be included in the responses if errors occur. + """ + # 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]] = {} + self.response_ids_by_type: dict[type[Response], 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 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 + + 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 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 + + # Go ahead and auto-register a few common response types + # if the user has not done so explicitly. Use unique IDs which + # will never change or overlap with user ids. + def _reg_if_not(reg_tp: type[Response], reg_id: int) -> None: + if reg_tp in self.response_ids_by_type: + return + assert self.response_types_by_id.get(reg_id) is None + self.response_types_by_id[reg_id] = reg_tp + self.response_ids_by_type[reg_tp] = reg_id + + _reg_if_not(ErrorResponse, -1) + _reg_if_not(EmptyResponse, -2) + # _reg_if_not(BoolResponse, -3) + + # Some extra-thorough validation in debug mode. + if __debug__: + # Make sure all Message types' return types are valid + # and have been assigned an ID as well. + all_response_types: set[type[Response]] = set() + for m_id, m_type in message_types.items(): + 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 len(set(m_rtypes)) == len(m_rtypes) # check dups + all_response_types.update(m_rtypes) + for cls in all_response_types: + assert is_ioprepped_dataclass(cls) + assert issubclass(cls, Response) + if cls not in self.response_ids_by_type: + raise ValueError(f'Possible response type {cls}' + f' needs to be included in response_types' + f' for this protocol.') + + # Make sure all registered types have unique base names. + # We can take advantage of this to generate cleaner looking + # protocol modules. Can revisit if this is ever a problem. + mtypenames = set(tp.__name__ for tp in self.message_ids_by_type) + if len(mtypenames) != len(message_types): + raise ValueError( + 'message_types contains duplicate __name__s;' + ' all types are required to have unique names.') + + self._type_key = type_key + self.preserve_clean_errors = preserve_clean_errors + self.log_remote_exceptions = log_remote_exceptions + self.trusted_sender = trusted_sender + + def encode_message(self, message: Message) -> str: + """Encode a message to a json string for transport.""" + return self._encode(message, self.message_ids_by_type, 'message') + + def encode_response(self, response: Response) -> str: + """Encode a response to a json string for transport.""" + return self._encode(response, self.response_ids_by_type, 'response') + + def _encode(self, message: Any, ids_by_type: dict[type, int], + opname: str) -> str: + """Encode a message to a json string for transport.""" + + m_id: Optional[int] = ids_by_type.get(type(message)) + if m_id is None: + raise TypeError(f'{opname} type is not registered in protocol:' + f' {type(message)}') + msgdict = dataclass_to_dict(message) + + # Encode type as part of the message/response dict if desired + # (for legacy compatibility). + if self._type_key is not None: + if self._type_key in msgdict: + raise RuntimeError(f'Type-key {self._type_key}' + f' found in msg of type {type(message)}') + msgdict[self._type_key] = m_id + out = msgdict + else: + out = {'m': msgdict, 't': m_id} + return json.dumps(out, separators=(',', ':')) + + def decode_message(self, data: str) -> Message: + """Decode a message from a json string.""" + out = self._decode(data, self.message_types_by_id, 'message') + assert isinstance(out, Message) + return out + + def decode_response(self, data: str) -> Optional[Response]: + """Decode a response from a json string.""" + out = self._decode(data, self.response_types_by_id, 'response') + assert isinstance(out, (Response, type(None))) + return out + + # Weeeird; we get mypy errors returning dict[int, type] but + # dict[int, typing.Type] or dict[int, type[Any]] works.. + def _decode(self, data: str, types_by_id: dict[int, type[Any]], + opname: str) -> Any: + """Decode a message from a json string.""" + msgfull = json.loads(data) + assert isinstance(msgfull, dict) + msgdict: Optional[dict] + if self._type_key is not None: + m_id = msgfull.pop(self._type_key) + msgdict = msgfull + assert isinstance(m_id, int) + else: + m_id = msgfull.get('t') + msgdict = msgfull.get('m') + assert isinstance(m_id, int) + assert isinstance(msgdict, dict) + + # Decode this particular type. + msgtype = types_by_id.get(m_id) + if msgtype is None: + raise TypeError(f'Got unregistered {opname} type id of {m_id}.') + out = dataclass_from_dict(msgtype, msgdict) + + # Special case: if we get EmptyResponse, we simply return None. + if isinstance(out, EmptyResponse): + return None + + # Special case: a remote error occurred. Raise a local Exception + # instead of returning the message. + if isinstance(out, ErrorResponse): + assert opname == 'response' + if (self.preserve_clean_errors + and out.error_type is ErrorType.CLEAN): + raise CleanError(out.error_message) + raise RemoteError(out.error_message) + + return out + + def _get_module_header(self, part: str) -> 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]] = {} + + single_message_type = len(self.message_ids_by_type) == 1 + + # Always import messages + for msgtype in list(self.message_ids_by_type) + [Message]: + tpimports.setdefault(msgtype.__module__, + []).append(msgtype.__name__) + for rsp_tp in list(self.response_ids_by_type) + [Response]: + # Skip these as they don't actually show up in code. + if rsp_tp is EmptyResponse or rsp_tp is ErrorResponse: + continue + 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__) + else: + tpimports.setdefault(rsp_tp.__module__, + []).append(rsp_tp.__name__) + + import_lines = '' + tpimport_lines = '' + + for module, names in sorted(imports.items()): + jnames = ', '.join(names) + line = f'from {module} import {jnames}' + if len(line) > 79: + # Recreate in a wrapping-friendly form. + line = f'from {module} import ({jnames})' + import_lines += f'{line}\n' + for module, names in sorted(tpimports.items()): + jnames = ', '.join(names) + line = f'from {module} import {jnames}' + if len(line) > 75: # Account for indent + # Recreate in a wrapping-friendly form. + line = f'from {module} import ({jnames})' + tpimport_lines += f'{line}\n' + + if part == 'sender': + 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)') + else: + import_lines += ('from efro.message import MessageReceiver,' + ' BoundMessageReceiver') + tpimport_typing_extras = ', Awaitable' + + ovld = ', overload' if not single_message_type else '' + tpimport_lines = textwrap.indent(tpimport_lines, ' ') + 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 Union, Any, Optional, Callable' + 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) -> str: + """Used by create_sender_module(); do not call directly.""" + # pylint: disable=too-many-locals + import textwrap + + msgtypes = list(self.message_ids_by_type.keys()) + + ppre = '_' if private else '' + out = self._get_module_header('sender') + 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') + + def _filt_tp_name(rtype: type[Response]) -> str: + # We accept None to equal EmptyResponse so reflect that + # in the type annotation. + return 'None' if rtype is EmptyResponse else rtype.__name__ + + # Define handler() overloads for all registered message types. + if msgtypes: + for async_pass in False, True: + if async_pass and not enable_async_sends: + continue + if not async_pass and not enable_sync_sends: + continue + pfx = 'async ' if async_pass else '' + sfx = '_async' if async_pass else '' + awt = 'await ' if async_pass else '' + how = 'asynchronously' if async_pass else 'synchronously' + + if len(msgtypes) == 1: + # Special case: with a single message types we don't + # use overloads. + msgtype = msgtypes[0] + msgtypevar = msgtype.__name__ + rtypes = msgtype.get_response_types() + if len(rtypes) > 1: + tps = ', '.join(_filt_tp_name(t) for t in rtypes) + rtypevar = f'Union[{tps}]' + 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') + else: + + for msgtype in msgtypes: + msgtypevar = msgtype.__name__ + rtypes = msgtype.get_response_types() + if len(rtypes) > 1: + tps = ', '.join(_filt_tp_name(t) for t in rtypes) + rtypevar = f'Union[{tps}]' + 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' -> Optional[Response]:\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) -> str: + """Used by create_receiver_module(); do not call directly.""" + # pylint: disable=too-many-locals + import textwrap + + desc = 'asynchronous' if is_async else 'synchronous' + ppre = '_' if private else '' + msgtypes = list(self.message_ids_by_type.keys()) + out = self._get_module_header('receiver') + 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') + + # Define handler() overloads for all registered message types. + + def _filt_tp_name(rtype: type[Response]) -> str: + # We accept None to equal EmptyResponse so reflect that + # in the type annotation. + return 'None' if rtype is EmptyResponse else rtype.__name__ + + if msgtypes: + cbgn = 'Awaitable[' if is_async else '' + cend = ']' if is_async else '' + if len(msgtypes) == 1: + # Special case: when we have a single message type we don't + # use overloads. + msgtype = msgtypes[0] + msgtypevar = msgtype.__name__ + rtypes = msgtype.get_response_types() + if len(rtypes) > 1: + tps = ', '.join(_filt_tp_name(t) for t in rtypes) + rtypevar = f'Union[{tps}]' + else: + rtypevar = _filt_tp_name(rtypes[0]) + rtypevar = f'{cbgn}{rtypevar}{cend}' + out += ( + f'\n' + f' def handler(\n' + f' self,\n' + f' call: Callable[[Any, {msgtypevar}], ' + f'{rtypevar}],\n' + f' )' + f' -> Callable[[Any, {msgtypevar}], {rtypevar}]:\n' + f' """Decorator to register message handlers."""\n' + f' from typing import cast, Callable, Any\n' + f' self.register_handler(cast(Callable' + f'[[Any, Message], Response], call))\n' + f' return call\n') + else: + for msgtype in msgtypes: + msgtypevar = msgtype.__name__ + rtypes = msgtype.get_response_types() + if len(rtypes) > 1: + tps = ', '.join(_filt_tp_name(t) for t in rtypes) + rtypevar = f'Union[{tps}]' + 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 += ( + '\n' + ' def handler(self, call: Callable) -> Callable:\n' + ' """Decorator to register message handlers."""\n' + ' self.register_handler(call)\n' + ' return call\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, message: str)' + ' -> str:\n' + ' """Asynchronously handle a raw incoming message."""\n' + ' return await' + ' self._receiver.handle_raw_message_async(\n' + ' self._obj, message)\n') + else: + out += ( + '\n' + ' def handle_raw_message(self, message: str) -> str:\n' + ' """Synchronously handle a raw incoming message."""\n' + ' return self._receiver.handle_raw_message' + '(self._obj, message)\n') + + return out + + +class MessageSender: + """Facilitates sending messages to a target and receiving responses. + This is instantiated at the class level and used to register unbound + class methods to handle raw message sending. + + Example: + + class MyClass: + msg = MyMessageSender(some_protocol) + + @msg.send_method + def send_raw_message(self, message: str) -> str: + # Actually send the message here. + + # MyMessageSender class should provide overloads for send(), send_bg(), + # etc. to ensure all sending happens with valid types. + obj = MyClass() + obj.msg.send(SomeMessageType()) + """ + + def __init__(self, protocol: MessageProtocol) -> None: + self.protocol = protocol + self._send_raw_message_call: Optional[Callable[[Any, str], str]] = None + self._send_async_raw_message_call: Optional[Callable[ + [Any, str], Awaitable[str]]] = None + + def send_method( + self, call: Callable[[Any, str], + str]) -> Callable[[Any, str], str]: + """Function decorator for setting raw send method.""" + assert self._send_raw_message_call is None + self._send_raw_message_call = call + return call + + def send_async_method( + self, call: Callable[[Any, str], Awaitable[str]] + ) -> Callable[[Any, str], Awaitable[str]]: + """Function decorator for setting raw send-async method.""" + assert self._send_async_raw_message_call is None + self._send_async_raw_message_call = call + return call + + def send(self, bound_obj: Any, message: Message) -> Optional[Response]: + """Send a message and receive a response. + + Will encode the message for transport and call dispatch_raw_message() + """ + if self._send_raw_message_call is None: + raise RuntimeError('send() is unimplemented for this type.') + + msg_encoded = self.protocol.encode_message(message) + response_encoded = self._send_raw_message_call(bound_obj, msg_encoded) + response = self.protocol.decode_response(response_encoded) + assert isinstance(response, (Response, type(None))) + assert (response is None + or type(response) in type(message).get_response_types()) + return response + + async def send_async(self, bound_obj: Any, + message: Message) -> Optional[Response]: + """Send a message asynchronously using asyncio. + + The message will be encoded for transport and passed to + dispatch_raw_message_async. + """ + if self._send_async_raw_message_call is None: + raise RuntimeError('send_async() is unimplemented for this type.') + + msg_encoded = self.protocol.encode_message(message) + response_encoded = await self._send_async_raw_message_call( + bound_obj, msg_encoded) + response = self.protocol.decode_response(response_encoded) + assert isinstance(response, (Response, type(None))) + assert (response is None + or type(response) in type(message).get_response_types()) + return response + + +class BoundMessageSender: + """Base class for bound senders.""" + + def __init__(self, obj: Any, sender: MessageSender) -> None: + assert obj is not None + self._obj = obj + self._sender = sender + + @property + def protocol(self) -> MessageProtocol: + """Protocol associated with this sender.""" + return self._sender.protocol + + def send_untyped(self, message: Message) -> Optional[Response]: + """Send a message synchronously. + + Whenever possible, use the send() call provided by generated + subclasses instead of this; it will provide better type safety. + """ + return self._sender.send(self._obj, message) + + async def send_async_untyped(self, message: Message) -> Optional[Response]: + """Send a message asynchronously. + + Whenever possible, use the send_async() call provided by generated + subclasses instead of this; it will provide better type safety. + """ + return await self._sender.send_async(self._obj, message) + + +class MessageReceiver: + """Facilitates receiving & responding to messages from a remote source. + + This is instantiated at the class level with unbound methods registered + as handlers for different message types in the protocol. + + Example: + + class MyClass: + receiver = MyMessageReceiver() + + # MyMessageReceiver fills out handler() overloads to ensure all + # registered handlers have valid types/return-types. + @receiver.handler + def handle_some_message_type(self, message: SomeMsg) -> SomeResponse: + # Deal with this message type here. + + # This will trigger the registered handler being called. + obj = MyClass() + obj.receiver.handle_raw_message(some_raw_data) + + Any unhandled Exception occurring during message handling will result in + an Exception being raised on the sending end. + """ + + is_async = False + + def __init__(self, protocol: MessageProtocol) -> None: + self.protocol = protocol + self._handlers: dict[type[Message], Callable] = {} + + # noinspection PyProtectedMember + def register_handler( + self, call: Callable[[Any, Message], Optional[Response]]) -> None: + """Register a handler call. + + The message type handled by the call is determined by its + type annotation. + """ + # TODO: can use types.GenericAlias in 3.9. + from typing import _GenericAlias # type: ignore + from typing import get_type_hints, get_args + + sig = inspect.getfullargspec(call) + + # 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}') + + # 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.') + raise ValueError(msg) + + # Check annotation types to determine what message types we handle. + # Return-type annotation can be a Union, but we probably don't + # have it available at runtime. Explicitly pull it in. + # UPDATE: we've updated our pylint filter to where we should + # have all annotations available. + # anns = get_type_hints(call, localns={'Union': Union}) + anns = get_type_hints(call) + + msgtype = anns.get('msg') + if not isinstance(msgtype, type): + raise TypeError( + f'expected a type for "msg" annotation; got {type(msgtype)}.') + assert issubclass(msgtype, Message) + + ret = anns.get('return') + responsetypes: tuple[Union[type[Any], type[None]], ...] + + # Return types can be a single type or a union of types. + if isinstance(ret, _GenericAlias): + targs = get_args(ret) + if not all(isinstance(a, type) for a in targs): + raise TypeError(f'expected only types for "return" annotation;' + f' got {targs}.') + responsetypes = targs + else: + if not isinstance(ret, type): + raise TypeError(f'expected one or more types for' + f' "return" annotation; got a {type(ret)}.') + responsetypes = (ret, ) + + # Return type of None translates to EmptyResponse. + responsetypes = tuple(EmptyResponse 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 + # of the supported types; can allow this in the future if it makes + # sense). + 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.') + + if msgtype in self._handlers: + 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()})') + + # Ok; we're good! + self._handlers[msgtype] = call + + def validate(self, warn_only: bool = False) -> None: + """Check for handler completeness, valid types, etc.""" + for msgtype in self.protocol.message_ids_by_type.keys(): + 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)}.') + if warn_only: + logging.warning(msg) + else: + raise TypeError(msg) + + def _decode_incoming_message(self, + msg: str) -> tuple[Message, type[Message]]: + # Decode the incoming message. + msg_decoded = self.protocol.decode_message(msg) + msgtype = type(msg_decoded) + assert issubclass(msgtype, Message) + return msg_decoded, msgtype + + def _encode_response(self, response: Optional[Response], + msgtype: type[Message]) -> str: + + # A return value of None equals EmptyResponse. + if response is None: + response = EmptyResponse() + + # Re-encode the response. + assert isinstance(response, Response) + # (user should never explicitly return these) + assert not isinstance(response, ErrorResponse) + assert type(response) in msgtype.get_response_types() + return self.protocol.encode_response(response) + + def raw_response_for_error(self, exc: Exception) -> str: + """Return a raw response for an error that occurred during handling.""" + if self.protocol.log_remote_exceptions: + logging.exception('Error handling message.') + + # If anything goes wrong, return a ErrorResponse instead. + if (isinstance(exc, CleanError) + and self.protocol.preserve_clean_errors): + err_response = ErrorResponse(error_message=str(exc), + error_type=ErrorType.CLEAN) + else: + err_response = ErrorResponse( + error_message=(traceback.format_exc() + if self.protocol.trusted_sender else + 'An unknown error has occurred.'), + error_type=ErrorType.OTHER) + return self.protocol.encode_response(err_response) + + def handle_raw_message(self, bound_obj: Any, msg: str) -> str: + """Decode, handle, and return an response for a message.""" + assert not self.is_async, "can't call sync handler on async receiver" + try: + msg_decoded, msgtype = self._decode_incoming_message(msg) + handler = self._handlers.get(msgtype) + if handler is None: + raise RuntimeError(f'Got unhandled message type: {msgtype}.') + result = handler(bound_obj, msg_decoded) + return self._encode_response(result, msgtype) + + except Exception as exc: + return self.raw_response_for_error(exc) + + async def handle_raw_message_async(self, bound_obj: Any, msg: str) -> str: + """Should be called when the receiver gets a message. + + The return value is the raw response to the message. + """ + assert self.is_async, "can't call async handler on sync receiver" + try: + msg_decoded, msgtype = self._decode_incoming_message(msg) + handler = self._handlers.get(msgtype) + if handler is None: + raise RuntimeError(f'Got unhandled message type: {msgtype}.') + result = await handler(bound_obj, msg_decoded) + return self._encode_response(result, msgtype) + + except Exception as exc: + return self.raw_response_for_error(exc) + + +class BoundMessageReceiver: + """Base bound receiver class.""" + + def __init__( + self, + obj: Any, + receiver: MessageReceiver, + ) -> None: + assert obj is not None + self._obj = obj + self._receiver = receiver + + @property + def protocol(self) -> MessageProtocol: + """Protocol associated with this receiver.""" + return self._receiver.protocol + + def raw_response_for_error(self, exc: Exception) -> str: + """Return a raw response for an error that occurred during handling. + + This is automatically called from standard handle_raw_message_x() + calls but can be manually invoked if errors occur outside of there. + This gives clients a better idea of what went wrong vs simply + returning invalid data which they might dismiss as a connection + related error. + """ + return self._receiver.raw_response_for_error(exc) + + +def create_sender_module(basename: str, + protocol_create_code: str, + enable_sync_sends: bool, + enable_async_sends: bool, + private: bool = False) -> str: + """Create a Python module defining a MessageSender subclass. + + This class is primarily for type checking and will contain overrides + for the varieties of send calls for message/response types defined + in the protocol. + + Code passed for 'protocol_create_code' should import necessary + modules and assign an instance of the Protocol to a 'protocol' + variable. + + Class names are based on basename; a basename 'FooSender' will + result in classes FooSender and BoundFooSender. + + If 'private' is True, class-names will be prefixed with an '_'. + + Note that line lengths are not clipped, so output may need to be + run through a formatter to prevent lint warnings about excessive + line lengths. + """ + + # Exec the passed code to get a protocol which we then use to + # generate module code. The user could simply call + # MessageProtocol.do_create_sender_module() directly, but this allows + # us to verify that the create code works and yields the protocol used + # to generate the code. + protocol = _protocol_from_code(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) + + +def create_receiver_module(basename: str, + protocol_create_code: str, + is_async: bool, + private: bool = False) -> str: + """"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 + the protocol. + + Class names are based on basename; a basename 'FooReceiver' will + result in FooReceiver and BoundFooReceiver. + + If 'is_async' is True, handle_raw_message() will be an async method + and the @handler decorator will expect async methods. + + If 'private' is True, class-names will be prefixed with an '_'. + + Note that line lengths are not clipped, so output may need to be + run through a formatter to prevent lint warnings about excessive + line lengths. + """ + # Exec the passed code to get a protocol which we then use to + # generate module code. The user could simply call + # MessageProtocol.do_create_sender_module() directly, but this allows + # us to verify that the create code works and yields the protocol used + # to generate the code. + protocol = _protocol_from_code(protocol_create_code) + return protocol.do_create_receiver_module( + basename=basename, + protocol_create_code=protocol_create_code, + is_async=is_async, + private=private) + + +def _protocol_from_code(protocol_create_code: str) -> MessageProtocol: + env: dict = {} + exec(protocol_create_code, env) # pylint: disable=exec-used + protocol = env.get('protocol') + if not isinstance(protocol, MessageProtocol): + raise RuntimeError( + f'protocol_create_code yielded' + f' a {type(protocol)}; expected a MessageProtocol instance.') + return protocol diff --git a/dist/ba_data/python/efro/terminal.py b/dist/ba_data/python/efro/terminal.py index 2fad689..cd06409 100644 --- a/dist/ba_data/python/efro/terminal.py +++ b/dist/ba_data/python/efro/terminal.py @@ -9,7 +9,7 @@ from enum import Enum, unique from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing import Any, ClassVar, Type + from typing import Any, ClassVar @unique @@ -113,7 +113,10 @@ def _windows_enable_color() -> bool: fdout = os.open('CONOUT$', os.O_RDWR) try: hout = msvcrt.get_osfhandle(fdout) # type: ignore + # pylint: disable=useless-suppression + # pylint: disable=no-value-for-parameter old_mode = wintypes.DWORD() + # pylint: enable=useless-suppression kernel32.GetConsoleMode(hout, ctypes.byref(old_mode)) mode = (new_mode & mask) | (old_mode.value & ~mask) kernel32.SetConsoleMode(hout, mode) @@ -298,7 +301,7 @@ 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()) -Clr: Type[ClrBase] +Clr: type[ClrBase] if _color_enabled: Clr = ClrAlways else: diff --git a/dist/ba_data/python/efro/util.py b/dist/ba_data/python/efro/util.py index 162d2aa..285505f 100644 --- a/dist/ba_data/python/efro/util.py +++ b/dist/ba_data/python/efro/util.py @@ -14,8 +14,7 @@ from typing import TYPE_CHECKING, cast, TypeVar, Generic if TYPE_CHECKING: import asyncio from efro.call import Call as Call # 'as Call' so we re-export. - from weakref import ReferenceType - from typing import Any, Dict, Callable, Optional, Type + from typing import Any, Callable, Optional T = TypeVar('T') TVAL = TypeVar('TVAL') @@ -35,7 +34,7 @@ else: Call = functools.partial -def enum_by_value(cls: Type[TENUM], value: Any) -> TENUM: +def enum_by_value(cls: type[TENUM], value: Any) -> TENUM: """Create an enum from a value. This is basically the same as doing 'obj = EnumType(value)' except @@ -45,6 +44,8 @@ def enum_by_value(cls: Type[TENUM], value: Any) -> TENUM: to our objects sticking around longer than we want. This issue has been submitted to Python as a bug so hopefully we can remove this eventually if it gets fixed: https://bugs.python.org/issue42248 + UPDATE: This has been fixed as of later 3.8 builds, so we can kill this + off once we are 3.9+ across the board. """ # Note: we don't recreate *ALL* the functionality of the Enum constructor @@ -56,6 +57,7 @@ def enum_by_value(cls: Type[TENUM], value: Any) -> TENUM: assert isinstance(out, cls) return out except KeyError: + # pylint: disable=consider-using-f-string raise ValueError('%r is not a valid %s' % (value, cls.__name__)) from None @@ -90,7 +92,18 @@ def utc_this_hour() -> datetime.datetime: tzinfo=now.tzinfo) -def empty_weakref(objtype: Type[T]) -> ReferenceType[T]: +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) + + +def empty_weakref(objtype: type[T]) -> weakref.ref[T]: """Return an invalidated weak-reference for the specified type.""" # At runtime, all weakrefs are the same; our type arg is just # for the static type checker. @@ -230,7 +243,7 @@ class DispatchMethodWrapper(Generic[TARG, TRET]): def register(func: Callable[[Any, Any], TRET]) -> Callable: """Register a new dispatch handler for this dispatch-method.""" - registry: Dict[Any, Callable] + registry: dict[Any, Callable] # noinspection PyProtectedMember,PyTypeHints @@ -291,7 +304,7 @@ class ValueDispatcher(Generic[TVAL, TRET]): def __init__(self, call: Callable[[TVAL], TRET]) -> None: self._base_call = call - self._handlers: Dict[TVAL, Callable[[], TRET]] = {} + self._handlers: dict[TVAL, Callable[[], TRET]] = {} def __call__(self, value: TVAL) -> TRET: handler = self._handlers.get(value) @@ -322,7 +335,7 @@ class ValueDispatcher1Arg(Generic[TVAL, TARG, TRET]): def __init__(self, call: Callable[[TVAL, TARG], TRET]) -> None: self._base_call = call - self._handlers: Dict[TVAL, Callable[[TARG], TRET]] = {} + self._handlers: dict[TVAL, Callable[[TARG], TRET]] = {} def __call__(self, value: TVAL, arg: TARG) -> TRET: handler = self._handlers.get(value) @@ -367,7 +380,7 @@ def valuedispatchmethod( # in the function call dict and simply return a call. _base_call = call - _handlers: Dict[TVAL, Callable[[TSELF], TRET]] = {} + _handlers: dict[TVAL, Callable[[TSELF], TRET]] = {} def _add_handler(value: TVAL, addcall: Callable[[TSELF], TRET]) -> None: if value in _handlers: @@ -391,7 +404,8 @@ def valuedispatchmethod( # To the type checker's eyes we return a ValueDispatchMethod instance; # this lets it know about our register func and type-check its usage. # In reality we just return a raw function call (for reasons listed above). - if TYPE_CHECKING: # pylint: disable=no-else-return + # pylint: disable=undefined-variable, no-else-return + if TYPE_CHECKING: return ValueDispatcherMethod[TVAL, TRET]() else: return _call_wrapper @@ -424,39 +438,79 @@ def make_hash(obj: Any) -> int: return hash(tuple(frozenset(sorted(new_obj.items())))) -def asserttype(obj: Any, typ: Type[T]) -> T: +def asserttype(obj: Any, typ: type[T]) -> T: """Return an object typed as a given type. Assert is used to check its actual type, so only use this when failures are not expected. Otherwise use checktype. """ + assert isinstance(typ, type), 'only actual types accepted' assert isinstance(obj, typ) return obj -def checktype(obj: Any, typ: Type[T]) -> T: +def asserttype_o(obj: Any, typ: type[T]) -> Optional[T]: + """Return an object typed as a given optional type. + + Assert is used to check its actual type, so only use this when + failures are not expected. Otherwise use checktype. + """ + assert isinstance(typ, type), 'only actual types accepted' + assert isinstance(obj, (typ, type(None))) + return obj + + +def checktype(obj: Any, typ: type[T]) -> T: """Return an object typed as a given type. Always checks the type at runtime with isinstance and throws a TypeError on failure. Use asserttype for more efficient (but less safe) equivalent. """ + assert isinstance(typ, type), 'only actual types accepted' if not isinstance(obj, typ): raise TypeError(f'Expected a {typ}; got a {type(obj)}.') return obj -def warntype(obj: Any, typ: Type[T]) -> T: +def checktype_o(obj: Any, typ: type[T]) -> Optional[T]: + """Return an object typed as a given optional type. + + Always checks the type at runtime with isinstance and throws a TypeError + on failure. Use asserttype for more efficient (but less safe) equivalent. + """ + assert isinstance(typ, type), 'only actual types accepted' + if not isinstance(obj, (typ, type(None))): + raise TypeError(f'Expected a {typ} or None; got a {type(obj)}.') + return obj + + +def warntype(obj: Any, typ: type[T]) -> T: """Return an object typed as a given type. Always checks the type at runtime and simply logs a warning if it is not what is expected. """ + 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 +def warntype_o(obj: Any, typ: type[T]) -> Optional[T]: + """Return an object typed as a given type. + + Always checks the type at runtime and simply logs a warning if it is + not what is expected. + """ + 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)) + return obj # type: ignore + + def assert_non_optional(obj: Optional[T]) -> T: """Return an object with Optional typing removed. @@ -495,3 +549,58 @@ def linearstep(edge0: float, edge1: float, x: float) -> float: Values outside of the range return 0 or 1. """ return max(0.0, min(1.0, (x - edge0) / (edge1 - edge0))) + + +def _compact_id(num: int, chars: str) -> str: + if num < 0: + raise ValueError('Negative integers not allowed.') + + # Chars must be in sorted order for sorting to work correctly + # on our output. + assert ''.join(sorted(list(chars))) == chars + + base = len(chars) + out = '' + while num: + out += chars[num % base] + num //= base + return out[::-1] or '0' + + +def human_readable_compact_id(num: int) -> str: + """Given a positive int, return a compact string representation for it. + + Handy for visualizing unique numeric ids using as few as possible chars. + This representation uses only lowercase letters and numbers (minus the + following letters for readability): + 's' is excluded due to similarity to '5'. + 'l' is excluded due to similarity to '1'. + 'i' is excluded due to similarity to '1'. + 'o' is excluded due to similarity to '0'. + 'z' is excluded due to similarity to '2'. + + When reading human input consisting of these IDs, it may be desirable + to map the disallowed chars to their corresponding allowed ones + ('o' -> '0', etc). + + Sort order for these ids is the same as the original numbers. + + If more compactness is desired at the expense of readability, + use compact_id() instead. + """ + return _compact_id(num, '0123456789abcdefghjkmnpqrtuvwxy') + + +def compact_id(num: int) -> str: + """Given a positive int, return a compact string representation for it. + + Handy for visualizing unique numeric ids using as few as possible chars. + This version is more compact than human_readable_compact_id() but less + friendly to humans due to using both capital and lowercase letters, + both 'O' and '0', etc. + + Sort order for these ids is the same as the original numbers. + """ + return _compact_id( + num, '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' + 'abcdefghijklmnopqrstuvwxyz') diff --git a/dist/ba_root/config.json b/dist/ba_root/config.json index 8d56848..24baa56 100644 --- a/dist/ba_root/config.json +++ b/dist/ba_root/config.json @@ -13,7 +13,7 @@ "Complete": false }, "Free Loader": { - "Complete": true + "Complete": false }, "Gold Miner": { "Complete": false @@ -127,6 +127,22 @@ "Auto Account State": "Server", "Auto Balance Teams": true, "Campaigns": {}, + "Custom Team Colors": [ + [ + 2.0, + 0.25, + 1.0 + ], + [ + 1.0, + 0.25, + 0.2 + ] + ], + "Custom Team Names": [ + "ladoo", + "barfi" + ], "Default Player Profiles": { "Client Input Device #1": "__account__", "Client Input Device #10": "__account__", @@ -141,441 +157,9 @@ "Free-for-All Max Players": 20, "Free-for-All Playlist Randomize": true, "Free-for-All Playlist Selection": "__default__", - "Free-for-All Playlists": { - "Default Free-for-All Playlist Copy": [ - { - "settings": { - "Epic Mode": false, - "Kills to Win Per Player": 10, - "Respawn Times": 1.0, - "Time Limit": 300, - "map": "Doom Shroom" - }, - "type": "bsDeathMatch.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" - }, - "type": "bsChosenOne.ChosenOneGame" - }, - { - "settings": { - "Hold Time": 30, - "Respawn Times": 1.0, - "Time Limit": 300, - "map": "Zigzag" - }, - "type": "bsKingOfTheHill.KingOfTheHillGame" - }, - { - "settings": { - "Epic Mode": false, - "map": "Rampage" - }, - "type": "bsMeteorShower.MeteorShowerGame" - }, - { - "settings": { - "Epic Mode": 1, - "Lives Per Player": 1, - "Respawn Times": 1.0, - "Time Limit": 120, - "map": "Tip Top" - }, - "type": "bsElimination.EliminationGame" - }, - { - "settings": { - "Hold Time": 30, - "Respawn Times": 1.0, - "Time Limit": 300, - "map": "The Pad" - }, - "type": "bsKeepAway.KeepAwayGame" - }, - { - "settings": { - "Epic Mode": true, - "Kills to Win Per Player": 10, - "Respawn Times": 0.25, - "Time Limit": 120, - "map": "Rampage" - }, - "type": "bsDeathMatch.DeathMatchGame" - }, - { - "settings": { - "Bomb Spawning": 1000, - "Epic Mode": false, - "Laps": 3, - "Mine Spawn Interval": 4000, - "Mine Spawning": 4000, - "Time Limit": 300, - "map": "Big G" - }, - "type": "bsRace.RaceGame" - }, - { - "settings": { - "Hold Time": 30, - "Respawn Times": 1.0, - "Time Limit": 300, - "map": "Happy Thoughts" - }, - "type": "bsKingOfTheHill.KingOfTheHillGame" - }, - { - "settings": { - "Enable Impact Bombs": 1, - "Enable Triple Bombs": false, - "Target Count": 2, - "map": "Doom Shroom" - }, - "type": "bsTargetPractice.TargetPracticeGame" - }, - { - "settings": { - "Epic Mode": false, - "Lives Per Player": 5, - "Respawn Times": 1.0, - "Time Limit": 300, - "map": "Step Right Up" - }, - "type": "bsElimination.EliminationGame" - }, - { - "settings": { - "Epic Mode": false, - "Kills to Win Per Player": 10, - "Respawn Times": 1.0, - "Time Limit": 300, - "map": "Crag Castle" - }, - "type": "bsDeathMatch.DeathMatchGame" - }, - { - "map": "Lake Frigid", - "settings": { - "Bomb Spawning": 0, - "Epic Mode": false, - "Laps": 6, - "Mine Spawning": 2000, - "Time Limit": 300, - "map": "Lake Frigid" - }, - "type": "bsRace.RaceGame" - } - ], - "My Free-for-All Playlist": [ - { - "settings": { - "Epic Mode": false, - "Frozen One Gets Gloves": true, - "Frozen One Gets Shield": true, - "Frozen One Time": 30, - "Respawn Times": 1.0, - "Time Limit": 0, - "map": "Zigzag" - }, - "type": "FrozenOne16.FrozenOneGame" - }, - { - "settings": { - "Pro Mode": true, - "map": "Tower D" - }, - "type": "bastd.game.easteregghunt.EasterEggHuntGame" - }, - { - "settings": { - "Epic Mode": false, - "map": "Rampage" - }, - "type": "ImpactTrigger.ImpactTriggerGame" - }, - { - "settings": { - "Epic Mode": false, - "Lives (0 = Unlimited)": 3, - "Respawn Times": 1.0, - "Time Limit": 0, - "map": "The Pad" - }, - "type": "SuperSmash.SuperSmash" - }, - { - "settings": { - "Epic Mode": false, - "Lives Per Player": 1, - "Max Zombies": 10, - "Respawn Times": 1.0, - "Time Limit": 0, - "map": "The Pad" - }, - "type": "ZombieHorde.ZombieHorde" - }, - { - "settings": { - "Allow Negative Scores": false, - "Epic Mode": false, - "Kills to Win Per Player": 5, - "Respawn Times": 1.0, - "Time Limit": 0, - "map": "Rampage" - }, - "type": "Yeeting-party.BoxingGame" - } - ], - "NEwFFAPlaylist": [ - { - "settings": { - "Allow Negative Scores": false, - "Epic Mode": false, - "Kills to Win Per Player": 10, - "Respawn Times": 1.0, - "Time Limit": 300, - "map": "Doom Shroom" - }, - "type": "bastd.game.deathmatch.DeathMatchGame" - }, - { - "settings": { - "Pro Mode": true, - "map": "Tower D" - }, - "type": "bastd.game.easteregghunt.EasterEggHuntGame" - }, - { - "settings": { - "Epic Mode": false, - "Frozen One Gets Gloves": true, - "Frozen One Gets Shield": true, - "Frozen One Time": 30, - "Respawn Times": 0.5, - "Time Limit": 600, - "map": "Doom Shroom" - }, - "type": "FrozenOne16.FrozenOneGame" - }, - { - "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": "bastd.game.chosenone.ChosenOneGame" - }, - { - "settings": { - "Hold Time": 30, - "Respawn Times": 1.0, - "Time Limit": 300, - "map": "Zigzag" - }, - "type": "bastd.game.kingofthehill.KingOfTheHillGame" - }, - { - "settings": { - "Epic Mode": false, - "map": "Rampage" - }, - "type": "bastd.game.meteorshower.MeteorShowerGame" - }, - { - "settings": { - "Allow Negative Scores": true, - "Epic Mode": false, - "Kills to Win Per Player": 5, - "Respawn Times": 0.5, - "Time Limit": 300, - "map": "Big G" - }, - "type": "Bombers.BombersGame" - }, - { - "settings": { - "Epic Mode": 1, - "Lives Per Player": 1, - "Respawn Times": 1.0, - "Time Limit": 120, - "map": "Tip Top" - }, - "type": "bastd.game.elimination.EliminationGame" - }, - { - "settings": { - "Epic Mode": true, - "map": "Rampage" - }, - "type": "ImpactTrigger.ImpactTriggerGame" - }, - { - "settings": { - "Allow Negative Scores": false, - "Epic Mode": false, - "Kills to Win Per Player": 5, - "Respawn Times": 1.0, - "Time Limit": 600, - "map": "Tip Top" - }, - "type": "Boxing.BoxingGame" - }, - { - "settings": { - "Enable Bombs": true, - "Epic Mode": false, - "Infection Spread Rate": 0.03, - "Max Infected Size": 6, - "Max Size Increases Every": 20, - "Mines": 10, - "Sec/Extra Mine": 10, - "map": "Football Stadium" - }, - "type": "Infection.Infection" - }, - { - "settings": { - "Hold Time": 30, - "Respawn Times": 1.0, - "Time Limit": 300, - "map": "The Pad" - }, - "type": "bastd.game.keepaway.KeepAwayGame" - }, - { - "settings": { - "Allow Negative Scores": false, - "Epic Mode": true, - "Kills to Win Per Player": 10, - "Respawn Times": 0.25, - "Time Limit": 120, - "map": "Rampage" - }, - "type": "bastd.game.deathmatch.DeathMatchGame" - }, - { - "settings": { - "Bomb Spawning": 1000, - "Epic Mode": false, - "Laps": 3, - "Mine Spawn Interval": 4000, - "Mine Spawning": 4000, - "Time Limit": 300, - "map": "Big G" - }, - "type": "bastd.game.race.RaceGame" - }, - { - "settings": { - "Epic Mode": false, - "map": "Football Stadium" - }, - "type": "running_bombs.RunningBombsGame" - }, - { - "settings": { - "Epic Mode": false, - "map": "Rampage" - }, - "type": "TnT_Error.TntErrorGame" - }, - { - "settings": { - "Epic Mode": true, - "Lives (0 = Unlimited)": 4, - "Respawn Times": 0.5, - "Time Limit": 600, - "map": "Step Right Up" - }, - "type": "SuperSmash.SuperSmash" - }, - { - "settings": { - "Hold Time": 30, - "Respawn Times": 1.0, - "Time Limit": 300, - "map": "Happy Thoughts" - }, - "type": "bastd.game.kingofthehill.KingOfTheHillGame" - }, - { - "settings": { - "Enable Impact Bombs": 1, - "Enable Triple Bombs": false, - "Target Count": 2, - "map": "Doom Shroom" - }, - "type": "bastd.game.targetpractice.TargetPracticeGame" - }, - { - "settings": { - "Allow Negative Scores": false, - "Epic Mode": false, - "Kills to Win Per Player": 5, - "Respawn Times": 1.0, - "Time Limit": 600, - "map": "Monkey Face" - }, - "type": "Yeeting-party.BoxingGame" - }, - { - "settings": { - "Epic Mode": false, - "Lives Per Player": 5, - "Respawn Times": 1.0, - "Time Limit": 300, - "map": "Step Right Up" - }, - "type": "bastd.game.elimination.EliminationGame" - }, - { - "settings": { - "Allow Negative Scores": false, - "Epic Mode": false, - "Kills to Win Per Player": 10, - "Respawn Times": 1.0, - "Time Limit": 300, - "map": "Crag Castle" - }, - "type": "bastd.game.deathmatch.DeathMatchGame" - }, - { - "settings": { - "Bomb Spawning": 0, - "Epic Mode": false, - "Laps": 6, - "Mine Spawning": 2000, - "Time Limit": 300, - "map": "Lake Frigid" - }, - "type": "bastd.game.race.RaceGame" - }, - { - "settings": { - "Epic Mode": false, - "Lives Per Player": 1, - "Max Zombies": 10, - "Respawn Times": 1.0, - "Time Limit": 600, - "map": "Football Stadium" - }, - "type": "ZombieHorde.ZombieHorde" - } - ] - }, + "Free-for-All Playlists": {}, "Idle Exit Minutes": null, - "Local Account Name": "Server751316", + "Local Account Name": "Server3258837", "PSTR": 0, "Player Profiles": { "__account__": { @@ -596,40 +180,39 @@ "bobmsquadhttpapi.HeySmoothy": { "enabled": true }, - "characters_duplicate.unlock_characters": { - "enabled": true - }, "importcustomcharacters.HeySmoothy": { "enabled": true } }, "Port": 43210, "Region Pings": { - "af-south-1": 362.8799051967982, - "ap-northeast-1": 195.93413298725014, - "ap-northeast-2": 167.69179541773173, - "ap-south-1": 49.67313368821493, - "ap-southeast-1": 102.81208971395539, - "ap-southeast-2": 207.70793292208856, - "ca-central-1": 258.46593754640077, - "eu-central-1": 187.78545635850745, - "eu-north-1": 269.71568798960567, - "eu-south-1": 167.59315978160802, - "eu-west-1": 243.81032149319657, - "eu-west-2": 202.43810049679897, - "eu-west-3": 174.47730615283058, - "me-south-1": 73.13938687201448, - "sa-east-1": 458.3799404716647, - "us-east-1": 253.64087897954357, - "us-east-2": 270.3062283767034, - "us-west-1": 290.62647223959715, - "us-west-2": 302.8684888728036 + "af-south-1": 310.4873846001974, + "ap-northeast-1": 160.38350760028334, + "ap-northeast-2": 151.66848159996152, + "ap-south-1": 42.999430000327266, + "ap-southeast-1": 85.54219160010689, + "ap-southeast-2": 177.78437579947786, + "ca-central-1": 235.28795540041756, + "eu-central-1": 157.53134780030814, + "eu-north-1": 178.57811620054417, + "eu-south-1": 148.7286152001907, + "eu-west-1": 174.8420205996481, + "eu-west-2": 164.6547662004923, + "eu-west-3": 163.58985420019962, + "me-south-1": 74.75811940041604, + "sa-east-1": 346.15190239976437, + "us-east-1": 225.80962139903568, + "us-east-2": 244.04948919953313, + "us-west-1": 267.5949521999537, + "us-west-2": 280.6645522005638 }, "Show Tutorial": false, "Signed In Last Session": false, "Team Game Max Players": 20, + "Team Tournament Playlist Randomize": true, + "Team Tournament Playlist Selection": "\u041a\u043e\u043f\u0438\u044f \u0421\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 \u043f\u043b\u0435\u0439-\u043b\u0438\u0441\u0442 \u0440\u0435\u0436\u0438\u043c\u0430 \u041a\u043e\u043c\u0430\u043d\u0434\u044b", "Team Tournament Playlists": { - "Default Teams Playlist Copy": [ + "\u041a\u043e\u043f\u0438\u044f \u0421\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 \u043f\u043b\u0435\u0439-\u043b\u0438\u0441\u0442 \u0440\u0435\u0436\u0438\u043c\u0430 \u041a\u043e\u043c\u0430\u043d\u0434\u044b": [ { "settings": { "Epic Mode": false, @@ -866,307 +449,9 @@ }, "type": "bsConquest.ConquestGame" } - ], - "Larz": [ - { - "settings": { - "Epic Mode": false, - "Flag Idle Return Time": 10, - "Flag Touch Return Time": 1, - "Respawn Times": 0.25, - "Score to Win": 3, - "Time Limit": 300, - "map": "Bridgit" - }, - "type": "bsCaptureTheFlag.CTFGame" - }, - { - "settings": { - "Epic Mode": true, - "Flag Idle Return Time": 5, - "Flag Touch Return Time": 2, - "Respawn Times": 0.25, - "Score to Win": 3, - "Time Limit": 120, - "map": "Rampage" - }, - "type": "bsCaptureTheFlag.CTFGame" - }, - { - "settings": { - "Balance Total Lives": true, - "Epic Mode": true, - "Lives Per Player": 5, - "Respawn Times": 0.25, - "Solo Mode": false, - "Time Limit": 300, - "map": "Courtyard" - }, - "type": "bsElimination.EliminationGame" - }, - { - "settings": { - "Epic Mode": false, - "Flag Idle Return Time": 15, - "Flag Touch Return Time": 15, - "Respawn Times": 0.25, - "Score to Win": 3, - "Time Limit": 600, - "map": "Zigzag" - }, - "type": "bsCaptureTheFlag.CTFGame" - }, - { - "settings": { - "Epic Mode": true, - "Respawn Times": 1.0, - "Time Limit": 600, - "map": "Happy Thoughts" - }, - "type": "bsConquest.ConquestGame" - }, - { - "settings": { - "Epic Mode": false, - "Respawn Times": 0.25, - "Time Limit": 600, - "map": "Zigzag" - }, - "type": "bsConquest.ConquestGame" - }, - { - "settings": { - "Epic Mode": false, - "Kills to Win Per Player": 6, - "Respawn Times": 0.25, - "Time Limit": 600, - "map": "Step Right Up" - }, - "type": "bsDeathMatch.DeathMatchGame" - }, - { - "settings": { - "Respawn Times": 0.25, - "Score to Win": 49, - "Time Limit": 120, - "map": "Football Stadium" - }, - "type": "bsFootball.FootballTeamGame" - }, - { - "settings": { - "Hold Time": 30, - "Respawn Times": 0.25, - "Time Limit": 600, - "map": "Football Stadium" - }, - "type": "bsKeepAway.KeepAwayGame" - }, - { - "settings": { - "Bomb Spawning": 1000, - "Entire Team Must Finish": false, - "Epic Mode": false, - "Laps": 3, - "Mine Spawning": 2000, - "Time Limit": 300, - "map": "Big G" - }, - "type": "bsRace.RaceGame" - }, - { - "settings": { - "Epic Mode": true, - "Respawn Times": 0.25, - "Score to Win": 5, - "Time Limit": 300, - "map": "Rampage" - }, - "type": "bsAssault.AssaultGame" - }, - { - "settings": { - "Epic Mode": false, - "Respawn Times": 0.25, - "Score to Win": 5, - "Time Limit": 600, - "map": "Step Right Up" - }, - "type": "bsAssault.AssaultGame" - }, - { - "settings": { - "Balance Total Lives": 1, - "Epic Mode": false, - "Lives Per Player": 3, - "Respawn Times": 0.25, - "Solo Mode": true, - "Time Limit": 600, - "map": "Rampage" - }, - "type": "bsElimination.EliminationGame" - }, - { - "settings": { - "Respawn Times": 2.0, - "Score to Win": 1, - "Time Limit": 300, - "map": "Hockey Stadium" - }, - "type": "bsHockey.HockeyGame" - }, - { - "settings": { - "Hold Time": 50, - "Respawn Times": 1.0, - "Time Limit": 120, - "map": "Monkey Face" - }, - "type": "bsKeepAway.KeepAwayGame" - }, - { - "settings": { - "Balance Total Lives": true, - "Epic Mode": true, - "Lives Per Player": 1, - "Respawn Times": 1.0, - "Solo Mode": true, - "Time Limit": 120, - "map": "Tip Top" - }, - "type": "bsElimination.EliminationGame" - }, - { - "settings": { - "Epic Mode": false, - "Respawn Times": 1.0, - "Score to Win": 3, - "Time Limit": 300, - "map": "Crag Castle" - }, - "type": "bsAssault.AssaultGame" - }, - { - "settings": { - "Epic Mode": false, - "Kills to Win Per Player": 10, - "Respawn Times": 0.25, - "Time Limit": 120, - "map": "Doom Shroom" - }, - "type": "bsDeathMatch.DeathMatchGame" - }, - { - "settings": { - "Epic Mode": false, - "Flag Idle Return Time": 30, - "Flag Touch Return Time": 0, - "Respawn Times": 1.0, - "Score to Win": 5, - "Time Limit": 600, - "map": "Roundabout" - }, - "type": "bsCaptureTheFlag.CTFGame" - }, - { - "settings": { - "Epic Mode": true, - "Respawn Times": 0.25, - "Score to Win": 5, - "Time Limit": 120, - "map": "Bridgit" - }, - "type": "bsAssault.AssaultGame" - }, - { - "settings": { - "Hold Time": 30, - "Respawn Times": 1.0, - "Time Limit": 300, - "map": "Tip Top" - }, - "type": "bsKingOfTheHill.KingOfTheHillGame" - }, - { - "settings": { - "Epic Mode": false, - "Respawn Times": 1.0, - "Score to Win": 2, - "Time Limit": 300, - "map": "Zigzag" - }, - "type": "bsAssault.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" - }, - "type": "bsCaptureTheFlag.CTFGame" - }, - { - "settings": { - "Bomb Spawning": 1000, - "Entire Team Must Finish": false, - "Epic Mode": true, - "Laps": 1, - "Mine Spawning": 2000, - "Time Limit": 300, - "map": "Big G" - }, - "type": "bsRace.RaceGame" - }, - { - "settings": { - "Epic Mode": false, - "Kills to Win Per Player": 5, - "Respawn Times": 1.0, - "Time Limit": 300, - "map": "Monkey Face" - }, - "type": "bsDeathMatch.DeathMatchGame" - }, - { - "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": "bsCaptureTheFlag.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" - }, - "type": "bsElimination.EliminationGame" - }, - { - "settings": { - "Epic Mode": true, - "Respawn Times": 0.25, - "Time Limit": 120, - "map": "Zigzag" - }, - "type": "bsConquest.ConquestGame" - } ] }, - "launchCount": 112, + "launchCount": 252, "lc14173": 1, "lc14292": 1 } \ No newline at end of file diff --git a/dist/ba_root/config.json.prev b/dist/ba_root/config.json.prev index 4cff72a..65787df 100644 --- a/dist/ba_root/config.json.prev +++ b/dist/ba_root/config.json.prev @@ -1 +1,460 @@ -{"Achievements": {"Boom Goes the Dynamite": {"Complete": false}, "Boxer": {"Complete": false}, "Dual Wielding": {"Complete": false}, "Flawless Victory": {"Complete": false}, "Free Loader": {"Complete": true}, "Gold Miner": {"Complete": false}, "Got the Moves": {"Complete": false}, "In Control": {"Complete": false}, "Last Stand God": {"Complete": false}, "Last Stand Master": {"Complete": false}, "Last Stand Wizard": {"Complete": false}, "Mine Games": {"Complete": false}, "Off You Go Then": {"Complete": false}, "Onslaught God": {"Complete": false}, "Onslaught Master": {"Complete": false}, "Onslaught Training Victory": {"Complete": false}, "Onslaught Wizard": {"Complete": false}, "Precision Bombing": {"Complete": false}, "Pro Boxer": {"Complete": false}, "Pro Football Shutout": {"Complete": false}, "Pro Football Victory": {"Complete": false}, "Pro Onslaught Victory": {"Complete": false}, "Pro Runaround Victory": {"Complete": false}, "Rookie Football Shutout": {"Complete": false}, "Rookie Football Victory": {"Complete": false}, "Rookie Onslaught Victory": {"Complete": false}, "Runaround God": {"Complete": false}, "Runaround Master": {"Complete": false}, "Runaround Wizard": {"Complete": false}, "Sharing is Caring": {"Complete": false}, "Stayin' Alive": {"Complete": false}, "Super Mega Punch": {"Complete": false}, "Super Punch": {"Complete": false}, "TNT Terror": {"Complete": false}, "Team Player": {"Complete": true}, "The Great Wall": {"Complete": false}, "The Wall": {"Complete": false}, "Uber Football Shutout": {"Complete": false}, "Uber Football Victory": {"Complete": false}, "Uber Onslaught Victory": {"Complete": false}, "Uber Runaround Victory": {"Complete": false}}, "Auto Account State": "Server", "Auto Balance Teams": true, "Campaigns": {}, "Default Player Profiles": {"Client Input Device #1": "__account__", "Client Input Device #10": "__account__", "Client Input Device #2": "__account__", "Client Input Device #3": "Goku", "Client Input Device #4": "\ud83d\udc30cute bunny\ud83d\udc9e", "Client Input Device #5": "__account__", "Client Input Device #6": "__account__", "Client Input Device #8": "AARAV SINGH", "Client Input Device #9": "__account__"}, "Free-for-All Max Players": 20, "Free-for-All Playlist Randomize": true, "Free-for-All Playlist Selection": "__default__", "Free-for-All Playlists": {"Default Free-for-All Playlist Copy": [{"settings": {"Epic Mode": false, "Kills to Win Per Player": 10, "Respawn Times": 1.0, "Time Limit": 300, "map": "Doom Shroom"}, "type": "bsDeathMatch.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"}, "type": "bsChosenOne.ChosenOneGame"}, {"settings": {"Hold Time": 30, "Respawn Times": 1.0, "Time Limit": 300, "map": "Zigzag"}, "type": "bsKingOfTheHill.KingOfTheHillGame"}, {"settings": {"Epic Mode": false, "map": "Rampage"}, "type": "bsMeteorShower.MeteorShowerGame"}, {"settings": {"Epic Mode": 1, "Lives Per Player": 1, "Respawn Times": 1.0, "Time Limit": 120, "map": "Tip Top"}, "type": "bsElimination.EliminationGame"}, {"settings": {"Hold Time": 30, "Respawn Times": 1.0, "Time Limit": 300, "map": "The Pad"}, "type": "bsKeepAway.KeepAwayGame"}, {"settings": {"Epic Mode": true, "Kills to Win Per Player": 10, "Respawn Times": 0.25, "Time Limit": 120, "map": "Rampage"}, "type": "bsDeathMatch.DeathMatchGame"}, {"settings": {"Bomb Spawning": 1000, "Epic Mode": false, "Laps": 3, "Mine Spawn Interval": 4000, "Mine Spawning": 4000, "Time Limit": 300, "map": "Big G"}, "type": "bsRace.RaceGame"}, {"settings": {"Hold Time": 30, "Respawn Times": 1.0, "Time Limit": 300, "map": "Happy Thoughts"}, "type": "bsKingOfTheHill.KingOfTheHillGame"}, {"settings": {"Enable Impact Bombs": 1, "Enable Triple Bombs": false, "Target Count": 2, "map": "Doom Shroom"}, "type": "bsTargetPractice.TargetPracticeGame"}, {"settings": {"Epic Mode": false, "Lives Per Player": 5, "Respawn Times": 1.0, "Time Limit": 300, "map": "Step Right Up"}, "type": "bsElimination.EliminationGame"}, {"settings": {"Epic Mode": false, "Kills to Win Per Player": 10, "Respawn Times": 1.0, "Time Limit": 300, "map": "Crag Castle"}, "type": "bsDeathMatch.DeathMatchGame"}, {"map": "Lake Frigid", "settings": {"Bomb Spawning": 0, "Epic Mode": false, "Laps": 6, "Mine Spawning": 2000, "Time Limit": 300, "map": "Lake Frigid"}, "type": "bsRace.RaceGame"}], "My Free-for-All Playlist": [{"settings": {"Epic Mode": false, "Frozen One Gets Gloves": true, "Frozen One Gets Shield": true, "Frozen One Time": 30, "Respawn Times": 1.0, "Time Limit": 0, "map": "Zigzag"}, "type": "FrozenOne16.FrozenOneGame"}, {"settings": {"Pro Mode": true, "map": "Tower D"}, "type": "bastd.game.easteregghunt.EasterEggHuntGame"}, {"settings": {"Epic Mode": false, "map": "Rampage"}, "type": "ImpactTrigger.ImpactTriggerGame"}, {"settings": {"Epic Mode": false, "Lives (0 = Unlimited)": 3, "Respawn Times": 1.0, "Time Limit": 0, "map": "The Pad"}, "type": "SuperSmash.SuperSmash"}, {"settings": {"Epic Mode": false, "Lives Per Player": 1, "Max Zombies": 10, "Respawn Times": 1.0, "Time Limit": 0, "map": "The Pad"}, "type": "ZombieHorde.ZombieHorde"}, {"settings": {"Allow Negative Scores": false, "Epic Mode": false, "Kills to Win Per Player": 5, "Respawn Times": 1.0, "Time Limit": 0, "map": "Rampage"}, "type": "Yeeting-party.BoxingGame"}], "NEwFFAPlaylist": [{"settings": {"Allow Negative Scores": false, "Epic Mode": false, "Kills to Win Per Player": 10, "Respawn Times": 1.0, "Time Limit": 300, "map": "Doom Shroom"}, "type": "bastd.game.deathmatch.DeathMatchGame"}, {"settings": {"Pro Mode": true, "map": "Tower D"}, "type": "bastd.game.easteregghunt.EasterEggHuntGame"}, {"settings": {"Epic Mode": false, "Frozen One Gets Gloves": true, "Frozen One Gets Shield": true, "Frozen One Time": 30, "Respawn Times": 0.5, "Time Limit": 600, "map": "Doom Shroom"}, "type": "FrozenOne16.FrozenOneGame"}, {"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": "bastd.game.chosenone.ChosenOneGame"}, {"settings": {"Hold Time": 30, "Respawn Times": 1.0, "Time Limit": 300, "map": "Zigzag"}, "type": "bastd.game.kingofthehill.KingOfTheHillGame"}, {"settings": {"Epic Mode": false, "map": "Rampage"}, "type": "bastd.game.meteorshower.MeteorShowerGame"}, {"settings": {"Allow Negative Scores": true, "Epic Mode": false, "Kills to Win Per Player": 5, "Respawn Times": 0.5, "Time Limit": 300, "map": "Big G"}, "type": "Bombers.BombersGame"}, {"settings": {"Epic Mode": 1, "Lives Per Player": 1, "Respawn Times": 1.0, "Time Limit": 120, "map": "Tip Top"}, "type": "bastd.game.elimination.EliminationGame"}, {"settings": {"Epic Mode": true, "map": "Rampage"}, "type": "ImpactTrigger.ImpactTriggerGame"}, {"settings": {"Allow Negative Scores": false, "Epic Mode": false, "Kills to Win Per Player": 5, "Respawn Times": 1.0, "Time Limit": 600, "map": "Tip Top"}, "type": "Boxing.BoxingGame"}, {"settings": {"Enable Bombs": true, "Epic Mode": false, "Infection Spread Rate": 0.03, "Max Infected Size": 6, "Max Size Increases Every": 20, "Mines": 10, "Sec/Extra Mine": 10, "map": "Football Stadium"}, "type": "Infection.Infection"}, {"settings": {"Hold Time": 30, "Respawn Times": 1.0, "Time Limit": 300, "map": "The Pad"}, "type": "bastd.game.keepaway.KeepAwayGame"}, {"settings": {"Allow Negative Scores": false, "Epic Mode": true, "Kills to Win Per Player": 10, "Respawn Times": 0.25, "Time Limit": 120, "map": "Rampage"}, "type": "bastd.game.deathmatch.DeathMatchGame"}, {"settings": {"Bomb Spawning": 1000, "Epic Mode": false, "Laps": 3, "Mine Spawn Interval": 4000, "Mine Spawning": 4000, "Time Limit": 300, "map": "Big G"}, "type": "bastd.game.race.RaceGame"}, {"settings": {"Epic Mode": false, "map": "Football Stadium"}, "type": "running_bombs.RunningBombsGame"}, {"settings": {"Epic Mode": false, "map": "Rampage"}, "type": "TnT_Error.TntErrorGame"}, {"settings": {"Epic Mode": true, "Lives (0 = Unlimited)": 4, "Respawn Times": 0.5, "Time Limit": 600, "map": "Step Right Up"}, "type": "SuperSmash.SuperSmash"}, {"settings": {"Hold Time": 30, "Respawn Times": 1.0, "Time Limit": 300, "map": "Happy Thoughts"}, "type": "bastd.game.kingofthehill.KingOfTheHillGame"}, {"settings": {"Enable Impact Bombs": 1, "Enable Triple Bombs": false, "Target Count": 2, "map": "Doom Shroom"}, "type": "bastd.game.targetpractice.TargetPracticeGame"}, {"settings": {"Allow Negative Scores": false, "Epic Mode": false, "Kills to Win Per Player": 5, "Respawn Times": 1.0, "Time Limit": 600, "map": "Monkey Face"}, "type": "Yeeting-party.BoxingGame"}, {"settings": {"Epic Mode": false, "Lives Per Player": 5, "Respawn Times": 1.0, "Time Limit": 300, "map": "Step Right Up"}, "type": "bastd.game.elimination.EliminationGame"}, {"settings": {"Allow Negative Scores": false, "Epic Mode": false, "Kills to Win Per Player": 10, "Respawn Times": 1.0, "Time Limit": 300, "map": "Crag Castle"}, "type": "bastd.game.deathmatch.DeathMatchGame"}, {"settings": {"Bomb Spawning": 0, "Epic Mode": false, "Laps": 6, "Mine Spawning": 2000, "Time Limit": 300, "map": "Lake Frigid"}, "type": "bastd.game.race.RaceGame"}, {"settings": {"Epic Mode": false, "Lives Per Player": 1, "Max Zombies": 10, "Respawn Times": 1.0, "Time Limit": 600, "map": "Football Stadium"}, "type": "ZombieHorde.ZombieHorde"}]}, "Idle Exit Minutes": null, "Local Account Name": "Server751316", "PSTR": 0, "Player Profiles": {"__account__": {"character": "Spaz", "color": [0.5, 0.25, 1.0], "highlight": [0.5, 0.25, 1.0]}}, "Plugins": {"bobmsquadhttpapi.HeySmoothy": {"enabled": true}, "characters_duplicate.unlock_characters": {"enabled": true}, "importcustomcharacters.HeySmoothy": {"enabled": true}}, "Port": 43210, "Region Pings": {"af-south-1": 362.8799051967982, "ap-northeast-1": 195.93413298725014, "ap-northeast-2": 167.69179541773173, "ap-south-1": 49.67313368821493, "ap-southeast-1": 102.81208971395539, "ap-southeast-2": 207.70793292208856, "ca-central-1": 258.46593754640077, "eu-central-1": 187.78545635850745, "eu-north-1": 269.71568798960567, "eu-south-1": 167.59315978160802, "eu-west-1": 243.81032149319657, "eu-west-2": 202.43810049679897, "eu-west-3": 174.47730615283058, "me-south-1": 73.13938687201448, "sa-east-1": 458.3799404716647, "us-east-1": 253.64087897954357, "us-east-2": 270.3062283767034, "us-west-1": 290.62647223959715, "us-west-2": 302.8684888728036}, "Show Tutorial": false, "Signed In Last Session": false, "Team Game Max Players": 20, "Team Tournament Playlists": {"Default Teams Playlist Copy": [{"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": "bsCaptureTheFlag.CTFGame"}, {"settings": {"Epic Mode": false, "Respawn Times": 1.0, "Score to Win": 3, "Time Limit": 600, "map": "Step Right Up"}, "type": "bsAssault.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"}, "type": "bsElimination.EliminationGame"}, {"settings": {"Epic Mode": false, "Kills to Win Per Player": 5, "Respawn Times": 1.0, "Time Limit": 300, "map": "Roundabout"}, "type": "bsDeathMatch.DeathMatchGame"}, {"settings": {"Respawn Times": 1.0, "Score to Win": 1, "Time Limit": 600, "map": "Hockey Stadium"}, "type": "bsHockey.HockeyGame"}, {"settings": {"Hold Time": 30, "Respawn Times": 1.0, "Time Limit": 300, "map": "Monkey Face"}, "type": "bsKeepAway.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"}, "type": "bsElimination.EliminationGame"}, {"settings": {"Epic Mode": false, "Respawn Times": 1.0, "Score to Win": 3, "Time Limit": 300, "map": "Crag Castle"}, "type": "bsAssault.AssaultGame"}, {"settings": {"Epic Mode": false, "Kills to Win Per Player": 5, "Respawn Times": 1.0, "Time Limit": 300, "map": "Doom Shroom"}, "type": "bsDeathMatch.DeathMatchGame"}, {"settings": {"Epic Mode": false, "map": "Rampage"}, "type": "bsMeteorShower.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"}, "type": "bsCaptureTheFlag.CTFGame"}, {"settings": {"Respawn Times": 1.0, "Score to Win": 21, "Time Limit": 600, "map": "Football Stadium"}, "type": "bsFootball.FootballTeamGame"}, {"settings": {"Epic Mode": true, "Respawn Times": 0.25, "Score to Win": 3, "Time Limit": 120, "map": "Bridgit"}, "type": "bsAssault.AssaultGame"}, {"map": "Doom Shroom", "settings": {"Enable Impact Bombs": 1, "Enable Triple Bombs": false, "Target Count": 2, "map": "Doom Shroom"}, "type": "bsTargetPractice.TargetPracticeGame"}, {"settings": {"Hold Time": 30, "Respawn Times": 1.0, "Time Limit": 300, "map": "Tip Top"}, "type": "bsKingOfTheHill.KingOfTheHillGame"}, {"settings": {"Epic Mode": false, "Respawn Times": 1.0, "Score to Win": 2, "Time Limit": 300, "map": "Zigzag"}, "type": "bsAssault.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"}, "type": "bsCaptureTheFlag.CTFGame"}, {"settings": {"Bomb Spawning": 1000, "Epic Mode": true, "Laps": 1, "Mine Spawning": 2000, "Time Limit": 300, "map": "Big G"}, "type": "bsRace.RaceGame"}, {"settings": {"Epic Mode": false, "Kills to Win Per Player": 5, "Respawn Times": 1.0, "Time Limit": 300, "map": "Monkey Face"}, "type": "bsDeathMatch.DeathMatchGame"}, {"settings": {"Hold Time": 30, "Respawn Times": 1.0, "Time Limit": 300, "map": "Lake Frigid"}, "type": "bsKeepAway.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"}, "type": "bsCaptureTheFlag.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"}, "type": "bsElimination.EliminationGame"}, {"settings": {"Epic Mode": true, "Respawn Times": 0.25, "Time Limit": 120, "map": "Zigzag"}, "type": "bsConquest.ConquestGame"}], "Larz": [{"settings": {"Epic Mode": false, "Flag Idle Return Time": 10, "Flag Touch Return Time": 1, "Respawn Times": 0.25, "Score to Win": 3, "Time Limit": 300, "map": "Bridgit"}, "type": "bsCaptureTheFlag.CTFGame"}, {"settings": {"Epic Mode": true, "Flag Idle Return Time": 5, "Flag Touch Return Time": 2, "Respawn Times": 0.25, "Score to Win": 3, "Time Limit": 120, "map": "Rampage"}, "type": "bsCaptureTheFlag.CTFGame"}, {"settings": {"Balance Total Lives": true, "Epic Mode": true, "Lives Per Player": 5, "Respawn Times": 0.25, "Solo Mode": false, "Time Limit": 300, "map": "Courtyard"}, "type": "bsElimination.EliminationGame"}, {"settings": {"Epic Mode": false, "Flag Idle Return Time": 15, "Flag Touch Return Time": 15, "Respawn Times": 0.25, "Score to Win": 3, "Time Limit": 600, "map": "Zigzag"}, "type": "bsCaptureTheFlag.CTFGame"}, {"settings": {"Epic Mode": true, "Respawn Times": 1.0, "Time Limit": 600, "map": "Happy Thoughts"}, "type": "bsConquest.ConquestGame"}, {"settings": {"Epic Mode": false, "Respawn Times": 0.25, "Time Limit": 600, "map": "Zigzag"}, "type": "bsConquest.ConquestGame"}, {"settings": {"Epic Mode": false, "Kills to Win Per Player": 6, "Respawn Times": 0.25, "Time Limit": 600, "map": "Step Right Up"}, "type": "bsDeathMatch.DeathMatchGame"}, {"settings": {"Respawn Times": 0.25, "Score to Win": 49, "Time Limit": 120, "map": "Football Stadium"}, "type": "bsFootball.FootballTeamGame"}, {"settings": {"Hold Time": 30, "Respawn Times": 0.25, "Time Limit": 600, "map": "Football Stadium"}, "type": "bsKeepAway.KeepAwayGame"}, {"settings": {"Bomb Spawning": 1000, "Entire Team Must Finish": false, "Epic Mode": false, "Laps": 3, "Mine Spawning": 2000, "Time Limit": 300, "map": "Big G"}, "type": "bsRace.RaceGame"}, {"settings": {"Epic Mode": true, "Respawn Times": 0.25, "Score to Win": 5, "Time Limit": 300, "map": "Rampage"}, "type": "bsAssault.AssaultGame"}, {"settings": {"Epic Mode": false, "Respawn Times": 0.25, "Score to Win": 5, "Time Limit": 600, "map": "Step Right Up"}, "type": "bsAssault.AssaultGame"}, {"settings": {"Balance Total Lives": 1, "Epic Mode": false, "Lives Per Player": 3, "Respawn Times": 0.25, "Solo Mode": true, "Time Limit": 600, "map": "Rampage"}, "type": "bsElimination.EliminationGame"}, {"settings": {"Respawn Times": 2.0, "Score to Win": 1, "Time Limit": 300, "map": "Hockey Stadium"}, "type": "bsHockey.HockeyGame"}, {"settings": {"Hold Time": 50, "Respawn Times": 1.0, "Time Limit": 120, "map": "Monkey Face"}, "type": "bsKeepAway.KeepAwayGame"}, {"settings": {"Balance Total Lives": true, "Epic Mode": true, "Lives Per Player": 1, "Respawn Times": 1.0, "Solo Mode": true, "Time Limit": 120, "map": "Tip Top"}, "type": "bsElimination.EliminationGame"}, {"settings": {"Epic Mode": false, "Respawn Times": 1.0, "Score to Win": 3, "Time Limit": 300, "map": "Crag Castle"}, "type": "bsAssault.AssaultGame"}, {"settings": {"Epic Mode": false, "Kills to Win Per Player": 10, "Respawn Times": 0.25, "Time Limit": 120, "map": "Doom Shroom"}, "type": "bsDeathMatch.DeathMatchGame"}, {"settings": {"Epic Mode": false, "Flag Idle Return Time": 30, "Flag Touch Return Time": 0, "Respawn Times": 1.0, "Score to Win": 5, "Time Limit": 600, "map": "Roundabout"}, "type": "bsCaptureTheFlag.CTFGame"}, {"settings": {"Epic Mode": true, "Respawn Times": 0.25, "Score to Win": 5, "Time Limit": 120, "map": "Bridgit"}, "type": "bsAssault.AssaultGame"}, {"settings": {"Hold Time": 30, "Respawn Times": 1.0, "Time Limit": 300, "map": "Tip Top"}, "type": "bsKingOfTheHill.KingOfTheHillGame"}, {"settings": {"Epic Mode": false, "Respawn Times": 1.0, "Score to Win": 2, "Time Limit": 300, "map": "Zigzag"}, "type": "bsAssault.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"}, "type": "bsCaptureTheFlag.CTFGame"}, {"settings": {"Bomb Spawning": 1000, "Entire Team Must Finish": false, "Epic Mode": true, "Laps": 1, "Mine Spawning": 2000, "Time Limit": 300, "map": "Big G"}, "type": "bsRace.RaceGame"}, {"settings": {"Epic Mode": false, "Kills to Win Per Player": 5, "Respawn Times": 1.0, "Time Limit": 300, "map": "Monkey Face"}, "type": "bsDeathMatch.DeathMatchGame"}, {"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": "bsCaptureTheFlag.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"}, "type": "bsElimination.EliminationGame"}, {"settings": {"Epic Mode": true, "Respawn Times": 0.25, "Time Limit": 120, "map": "Zigzag"}, "type": "bsConquest.ConquestGame"}]}, "launchCount": 111, "lc14173": 1, "lc14292": 1} \ No newline at end of file +{ + "Achievements": { + "Boom Goes the Dynamite": { + "Complete": false + }, + "Boxer": { + "Complete": false + }, + "Dual Wielding": { + "Complete": false + }, + "Flawless Victory": { + "Complete": false + }, + "Free Loader": { + "Complete": false + }, + "Gold Miner": { + "Complete": false + }, + "Got the Moves": { + "Complete": false + }, + "In Control": { + "Complete": false + }, + "Last Stand God": { + "Complete": false + }, + "Last Stand Master": { + "Complete": false + }, + "Last Stand Wizard": { + "Complete": false + }, + "Mine Games": { + "Complete": false + }, + "Off You Go Then": { + "Complete": false + }, + "Onslaught God": { + "Complete": false + }, + "Onslaught Master": { + "Complete": false + }, + "Onslaught Training Victory": { + "Complete": false + }, + "Onslaught Wizard": { + "Complete": false + }, + "Precision Bombing": { + "Complete": false + }, + "Pro Boxer": { + "Complete": false + }, + "Pro Football Shutout": { + "Complete": false + }, + "Pro Football Victory": { + "Complete": false + }, + "Pro Onslaught Victory": { + "Complete": false + }, + "Pro Runaround Victory": { + "Complete": false + }, + "Rookie Football Shutout": { + "Complete": false + }, + "Rookie Football Victory": { + "Complete": false + }, + "Rookie Onslaught Victory": { + "Complete": false + }, + "Runaround God": { + "Complete": false + }, + "Runaround Master": { + "Complete": false + }, + "Runaround Wizard": { + "Complete": false + }, + "Sharing is Caring": { + "Complete": false + }, + "Stayin' Alive": { + "Complete": false + }, + "Super Mega Punch": { + "Complete": false + }, + "Super Punch": { + "Complete": false + }, + "TNT Terror": { + "Complete": false + }, + "Team Player": { + "Complete": true + }, + "The Great Wall": { + "Complete": false + }, + "The Wall": { + "Complete": false + }, + "Uber Football Shutout": { + "Complete": false + }, + "Uber Football Victory": { + "Complete": false + }, + "Uber Onslaught Victory": { + "Complete": false + }, + "Uber Runaround Victory": { + "Complete": false + } + }, + "Auto Account State": "Server", + "Auto Balance Teams": true, + "Campaigns": {}, + "Custom Team Colors": [ + [ + 2.0, + 0.25, + 1.0 + ], + [ + 1.0, + 0.25, + 0.2 + ] + ], + "Custom Team Names": [ + "ladoo", + "barfi" + ], + "Default Player Profiles": { + "Client Input Device #1": "__account__", + "Client Input Device #10": "__account__", + "Client Input Device #2": "__account__", + "Client Input Device #3": "Goku", + "Client Input Device #4": "\ud83d\udc30cute bunny\ud83d\udc9e", + "Client Input Device #5": "__account__", + "Client Input Device #6": "__account__", + "Client Input Device #8": "AARAV SINGH", + "Client Input Device #9": "__account__" + }, + "Free-for-All Max Players": 20, + "Free-for-All Playlist Randomize": true, + "Free-for-All Playlist Selection": "__default__", + "Free-for-All Playlists": {}, + "Idle Exit Minutes": null, + "Local Account Name": "Server3258837", + "PSTR": 0, + "Player Profiles": { + "__account__": { + "character": "Spaz", + "color": [ + 0.5, + 0.25, + 1.0 + ], + "highlight": [ + 0.5, + 0.25, + 1.0 + ] + } + }, + "Plugins": { + "bobmsquadhttpapi.HeySmoothy": { + "enabled": true + }, + "characters_duplicate.unlock_characters": { + "enabled": true + }, + "importcustomcharacters.HeySmoothy": { + "enabled": true + } + }, + "Port": 43210, + "Region Pings": { + "af-south-1": 310.3448000001663, + "ap-northeast-1": 166.10779999973602, + "ap-northeast-2": 151.27690000008442, + "ap-south-1": 42.999430000327266, + "ap-southeast-1": 85.54219160010689, + "ap-southeast-2": 179.14919999930135, + "ca-central-1": 234.70910000105505, + "eu-central-1": 161.19810000054713, + "eu-north-1": 181.7263000011735, + "eu-south-1": 147.98619999965013, + "eu-west-1": 170.556499999293, + "eu-west-2": 166.20810000131314, + "eu-west-3": 168.4557999997196, + "me-south-1": 74.75811940041604, + "sa-east-1": 347.53679999994347, + "us-east-1": 226.50029999931576, + "us-east-2": 251.41959999928076, + "us-west-1": 267.6188999994338, + "us-west-2": 277.7994000007311 + }, + "Show Tutorial": false, + "Signed In Last Session": false, + "Team Game Max Players": 20, + "Team Tournament Playlist Randomize": true, + "Team Tournament Playlist Selection": "\u041a\u043e\u043f\u0438\u044f \u0421\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 \u043f\u043b\u0435\u0439-\u043b\u0438\u0441\u0442 \u0440\u0435\u0436\u0438\u043c\u0430 \u041a\u043e\u043c\u0430\u043d\u0434\u044b", + "Team Tournament Playlists": { + "\u041a\u043e\u043f\u0438\u044f \u0421\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439 \u043f\u043b\u0435\u0439-\u043b\u0438\u0441\u0442 \u0440\u0435\u0436\u0438\u043c\u0430 \u041a\u043e\u043c\u0430\u043d\u0434\u044b": [ + { + "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": "bsCaptureTheFlag.CTFGame" + }, + { + "settings": { + "Epic Mode": false, + "Respawn Times": 1.0, + "Score to Win": 3, + "Time Limit": 600, + "map": "Step Right Up" + }, + "type": "bsAssault.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" + }, + "type": "bsElimination.EliminationGame" + }, + { + "settings": { + "Epic Mode": false, + "Kills to Win Per Player": 5, + "Respawn Times": 1.0, + "Time Limit": 300, + "map": "Roundabout" + }, + "type": "bsDeathMatch.DeathMatchGame" + }, + { + "settings": { + "Respawn Times": 1.0, + "Score to Win": 1, + "Time Limit": 600, + "map": "Hockey Stadium" + }, + "type": "bsHockey.HockeyGame" + }, + { + "settings": { + "Hold Time": 30, + "Respawn Times": 1.0, + "Time Limit": 300, + "map": "Monkey Face" + }, + "type": "bsKeepAway.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" + }, + "type": "bsElimination.EliminationGame" + }, + { + "settings": { + "Epic Mode": false, + "Respawn Times": 1.0, + "Score to Win": 3, + "Time Limit": 300, + "map": "Crag Castle" + }, + "type": "bsAssault.AssaultGame" + }, + { + "settings": { + "Epic Mode": false, + "Kills to Win Per Player": 5, + "Respawn Times": 1.0, + "Time Limit": 300, + "map": "Doom Shroom" + }, + "type": "bsDeathMatch.DeathMatchGame" + }, + { + "settings": { + "Epic Mode": false, + "map": "Rampage" + }, + "type": "bsMeteorShower.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" + }, + "type": "bsCaptureTheFlag.CTFGame" + }, + { + "settings": { + "Respawn Times": 1.0, + "Score to Win": 21, + "Time Limit": 600, + "map": "Football Stadium" + }, + "type": "bsFootball.FootballTeamGame" + }, + { + "settings": { + "Epic Mode": true, + "Respawn Times": 0.25, + "Score to Win": 3, + "Time Limit": 120, + "map": "Bridgit" + }, + "type": "bsAssault.AssaultGame" + }, + { + "map": "Doom Shroom", + "settings": { + "Enable Impact Bombs": 1, + "Enable Triple Bombs": false, + "Target Count": 2, + "map": "Doom Shroom" + }, + "type": "bsTargetPractice.TargetPracticeGame" + }, + { + "settings": { + "Hold Time": 30, + "Respawn Times": 1.0, + "Time Limit": 300, + "map": "Tip Top" + }, + "type": "bsKingOfTheHill.KingOfTheHillGame" + }, + { + "settings": { + "Epic Mode": false, + "Respawn Times": 1.0, + "Score to Win": 2, + "Time Limit": 300, + "map": "Zigzag" + }, + "type": "bsAssault.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" + }, + "type": "bsCaptureTheFlag.CTFGame" + }, + { + "settings": { + "Bomb Spawning": 1000, + "Epic Mode": true, + "Laps": 1, + "Mine Spawning": 2000, + "Time Limit": 300, + "map": "Big G" + }, + "type": "bsRace.RaceGame" + }, + { + "settings": { + "Epic Mode": false, + "Kills to Win Per Player": 5, + "Respawn Times": 1.0, + "Time Limit": 300, + "map": "Monkey Face" + }, + "type": "bsDeathMatch.DeathMatchGame" + }, + { + "settings": { + "Hold Time": 30, + "Respawn Times": 1.0, + "Time Limit": 300, + "map": "Lake Frigid" + }, + "type": "bsKeepAway.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" + }, + "type": "bsCaptureTheFlag.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" + }, + "type": "bsElimination.EliminationGame" + }, + { + "settings": { + "Epic Mode": true, + "Respawn Times": 0.25, + "Time Limit": 120, + "map": "Zigzag" + }, + "type": "bsConquest.ConquestGame" + } + ] + }, + "launchCount": 252, + "lc14173": 1, + "lc14292": 1 +} \ No newline at end of file diff --git a/dist/ba_root/mods/__pycache__/bobmsquadhttpapi.cpython-39.pyc b/dist/ba_root/mods/__pycache__/bobmsquadhttpapi.cpython-39.pyc new file mode 100644 index 0000000..be0dd22 Binary files /dev/null and b/dist/ba_root/mods/__pycache__/bobmsquadhttpapi.cpython-39.pyc differ diff --git a/dist/ba_root/mods/__pycache__/characters_duplicate.cpython-39.pyc b/dist/ba_root/mods/__pycache__/characters_duplicate.cpython-39.pyc new file mode 100644 index 0000000..fc21220 Binary files /dev/null and b/dist/ba_root/mods/__pycache__/characters_duplicate.cpython-39.pyc differ diff --git a/dist/ba_root/mods/__pycache__/custom_hooks.cpython-39.pyc b/dist/ba_root/mods/__pycache__/custom_hooks.cpython-39.pyc new file mode 100644 index 0000000..d4b70f6 Binary files /dev/null and b/dist/ba_root/mods/__pycache__/custom_hooks.cpython-39.pyc differ diff --git a/dist/ba_root/mods/__pycache__/importcustomcharacters.cpython-39.pyc b/dist/ba_root/mods/__pycache__/importcustomcharacters.cpython-39.pyc new file mode 100644 index 0000000..916b9a5 Binary files /dev/null and b/dist/ba_root/mods/__pycache__/importcustomcharacters.cpython-39.pyc differ diff --git a/dist/ba_root/mods/__pycache__/setting.cpython-39.pyc b/dist/ba_root/mods/__pycache__/setting.cpython-39.pyc new file mode 100644 index 0000000..9b7cc16 Binary files /dev/null and b/dist/ba_root/mods/__pycache__/setting.cpython-39.pyc differ diff --git a/dist/ba_root/mods/chatHandle/ChatCommands/Main.py b/dist/ba_root/mods/chatHandle/ChatCommands/Main.py index 4f393ca..e06e6d6 100644 --- a/dist/ba_root/mods/chatHandle/ChatCommands/Main.py +++ b/dist/ba_root/mods/chatHandle/ChatCommands/Main.py @@ -12,7 +12,7 @@ from .Handlers import check_permissions import ba, _ba import setting - +from serverData import serverdata def command_type(command): @@ -89,7 +89,10 @@ def Command(msg, clientid): settings = setting.get_settings_data() - + if accountid in serverdata.clients: + if serverdata.clients[accountid]["isMuted"]: + _ba.screenmessage("You are on mute", transient=True, clients=[clientid]) + return None if settings["ChatCommands"]["BrodcastCommand"]: return msg return None diff --git a/dist/ba_root/mods/chatHandle/ChatCommands/__pycache__/Handlers.cpython-39.pyc b/dist/ba_root/mods/chatHandle/ChatCommands/__pycache__/Handlers.cpython-39.pyc new file mode 100644 index 0000000..68a21d9 Binary files /dev/null and b/dist/ba_root/mods/chatHandle/ChatCommands/__pycache__/Handlers.cpython-39.pyc differ diff --git a/dist/ba_root/mods/chatHandle/ChatCommands/__pycache__/Main.cpython-39.pyc b/dist/ba_root/mods/chatHandle/ChatCommands/__pycache__/Main.cpython-39.pyc new file mode 100644 index 0000000..d04b0cf Binary files /dev/null and b/dist/ba_root/mods/chatHandle/ChatCommands/__pycache__/Main.cpython-39.pyc differ diff --git a/dist/ba_root/mods/chatHandle/ChatCommands/__pycache__/__init__.cpython-39.pyc b/dist/ba_root/mods/chatHandle/ChatCommands/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..8249844 Binary files /dev/null and b/dist/ba_root/mods/chatHandle/ChatCommands/__pycache__/__init__.cpython-39.pyc differ diff --git a/dist/ba_root/mods/chatHandle/ChatCommands/commands/Management.py b/dist/ba_root/mods/chatHandle/ChatCommands/commands/Management.py index 9685a80..508b8a7 100644 --- a/dist/ba_root/mods/chatHandle/ChatCommands/commands/Management.py +++ b/dist/ba_root/mods/chatHandle/ChatCommands/commands/Management.py @@ -1,11 +1,11 @@ from .Handlers import handlemsg, handlemsg_all,send from playersData import pdata from tools.whitelist import add_to_white_list, add_commit_to_logs - +from serverData import serverdata import ba, _ba, time, setting -Commands = ['kick', 'remove', 'end', 'quit', 'mute', 'unmute', 'slowmo', 'nv', 'dv', 'pause', 'cameramode', 'createrole', 'addrole', 'removerole', 'addcommand', 'addcmd', 'removecommand','getroles', 'removecmd', 'changetag','customtag','customeffect','add', 'spectators', 'lobbytime'] +Commands = ['ban','kick', 'remove', 'end', 'quit', 'mute', 'unmute', 'slowmo', 'nv', 'dv', 'pause', 'cameramode', 'createrole', 'addrole', 'removerole', 'addcommand', 'addcmd', 'removecommand','getroles', 'removecmd', 'changetag','customtag','customeffect','add', 'spectators', 'lobbytime'] CommandAliases = ['rm', 'next', 'restart', 'mutechat', 'unmutechat', 'sm', 'slow', 'night', 'day', 'pausegame', 'camera_mode', 'rotate_camera', 'whitelist','effect'] @@ -23,9 +23,11 @@ def ExcelCommand(command, arguments, clientid, accountid): Returns: None """ + if command == 'kick': kick(arguments) - + elif command == 'ban': + ban(arguments) elif command in ['end', 'next']: end(arguments) @@ -33,10 +35,10 @@ def ExcelCommand(command, arguments, clientid, accountid): quit(arguments) elif command in ['mute', 'mutechat']: - mute() + mute(arguments) elif command in ['unmute', 'unmutechat']: - un_mute() + un_mute(arguments) elif command in ['remove', 'rm']: remove(arguments) @@ -111,6 +113,20 @@ def end(arguments): except: pass +def ban(arguments): + try: + cl_id=int(arguments[0]) + ac_id="" + for ros in _ba.get_game_roster(): + if ros["client_id"]==cl_id: + pdata.ban_player(ros['account_id']) + ac_id=ros['account_id'] + if ac_id in serverdata.clients: + serverdata.clients[ac_id]["isBan"]=True + kick(arguments) + except: + pass + def quit(arguments): @@ -120,13 +136,35 @@ def quit(arguments): -def mute(): +def mute(arguments): + try: + cl_id=int(arguments[0]) + ac_id="" + for ros in _ba.get_game_roster(): + if ros["client_id"]==cl_id: + pdata.mute(ros['account_id']) + ac_id=ros['account_id'] + if ac_id in serverdata.clients: + serverdata.clients[ac_id]["isMuted"]=True + except: + pass return -def un_mute(): - return +def un_mute(arguments): + try: + cl_id=int(arguments[0]) + ac_id="" + for ros in _ba.get_game_roster(): + if ros["client_id"]==cl_id: + pdata.unmute(ros['account_id']) + ac_id=ros['account_id'] + if ac_id in serverdata.clients: + serverdata.clients[ac_id]["isMuted"]=False + return + except: + pass diff --git a/dist/ba_root/mods/chatHandle/ChatCommands/commands/__pycache__/Cheats.cpython-39.pyc b/dist/ba_root/mods/chatHandle/ChatCommands/commands/__pycache__/Cheats.cpython-39.pyc new file mode 100644 index 0000000..9cca100 Binary files /dev/null and b/dist/ba_root/mods/chatHandle/ChatCommands/commands/__pycache__/Cheats.cpython-39.pyc differ diff --git a/dist/ba_root/mods/chatHandle/ChatCommands/commands/__pycache__/Fun.cpython-39.pyc b/dist/ba_root/mods/chatHandle/ChatCommands/commands/__pycache__/Fun.cpython-39.pyc new file mode 100644 index 0000000..336f241 Binary files /dev/null and b/dist/ba_root/mods/chatHandle/ChatCommands/commands/__pycache__/Fun.cpython-39.pyc differ diff --git a/dist/ba_root/mods/chatHandle/ChatCommands/commands/__pycache__/Handlers.cpython-39.pyc b/dist/ba_root/mods/chatHandle/ChatCommands/commands/__pycache__/Handlers.cpython-39.pyc new file mode 100644 index 0000000..8bd95bc Binary files /dev/null and b/dist/ba_root/mods/chatHandle/ChatCommands/commands/__pycache__/Handlers.cpython-39.pyc differ diff --git a/dist/ba_root/mods/chatHandle/ChatCommands/commands/__pycache__/Management.cpython-39.pyc b/dist/ba_root/mods/chatHandle/ChatCommands/commands/__pycache__/Management.cpython-39.pyc new file mode 100644 index 0000000..d3f2c0f Binary files /dev/null and b/dist/ba_root/mods/chatHandle/ChatCommands/commands/__pycache__/Management.cpython-39.pyc differ diff --git a/dist/ba_root/mods/chatHandle/ChatCommands/commands/__pycache__/NormalCommands.cpython-39.pyc b/dist/ba_root/mods/chatHandle/ChatCommands/commands/__pycache__/NormalCommands.cpython-39.pyc new file mode 100644 index 0000000..bc96ad8 Binary files /dev/null and b/dist/ba_root/mods/chatHandle/ChatCommands/commands/__pycache__/NormalCommands.cpython-39.pyc differ diff --git a/dist/ba_root/mods/chatHandle/__pycache__/__init__.cpython-39.pyc b/dist/ba_root/mods/chatHandle/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..eeabab1 Binary files /dev/null and b/dist/ba_root/mods/chatHandle/__pycache__/__init__.cpython-39.pyc differ diff --git a/dist/ba_root/mods/chatHandle/__pycache__/handlechat.cpython-39.pyc b/dist/ba_root/mods/chatHandle/__pycache__/handlechat.cpython-39.pyc new file mode 100644 index 0000000..7f69007 Binary files /dev/null and b/dist/ba_root/mods/chatHandle/__pycache__/handlechat.cpython-39.pyc differ diff --git a/dist/ba_root/mods/chatHandle/handlechat.py b/dist/ba_root/mods/chatHandle/handlechat.py index 7f2b407..a9351f9 100644 --- a/dist/ba_root/mods/chatHandle/handlechat.py +++ b/dist/ba_root/mods/chatHandle/handlechat.py @@ -1,13 +1,16 @@ # Released under the MIT License. See LICENSE for details. from playersData import pdata +from serverData import serverdata from chatHandle.ChatCommands import Main from tools import Logger import ba, _ba +import setting - +settings = setting.get_settings_data() def filter_chat_message(msg, client_id): + if msg.startswith("/"): return Main.Command(msg, client_id) acid="" @@ -15,7 +18,22 @@ def filter_chat_message(msg, client_id): if i['client_id'] == client_id: acid = i['account_id'] Logger.log(acid+" | "+msg,"chat") - return msg + + if acid in serverdata.clients: + if serverdata.clients[acid]["isMuted"]: + _ba.screenmessage("You are on mute", transient=True, clients=[client_id]) + return None + elif serverdata.clients[acid]["accountAge"] < settings['minAgeToChatInHours']: + _ba.screenmessage("New accounts not allowed to chat here", transient=True, clients=[client_id]) + return None + else: + return msg + + + else: + _ba.screenmessage("Fetching your account info , Wait a minute", transient=True, clients=[client_id]) + return None + """ if chatfilter.isAbuse(msg): diff --git a/dist/ba_root/mods/custom_hooks.py b/dist/ba_root/mods/custom_hooks.py index 97a009e..36598f4 100644 --- a/dist/ba_root/mods/custom_hooks.py +++ b/dist/ba_root/mods/custom_hooks.py @@ -1,13 +1,17 @@ -from chatHandle import handlechat +import ba +import _ba +from chatHandle import handlechat +import setting def filter_chat_message(msg, client_id): - - return handlechat.filter_chat_message(msg, client_id) + + return handlechat.filter_chat_message(msg, client_id) def on_app_launch(): - from tools import whitelist - whitelist.Whitelist() + from tools import whitelist + whitelist.Whitelist() + bootstraping() #something @@ -19,3 +23,22 @@ def playerspaz_init(player): pass #add tag,rank,effect + + + + +def bootstraping(): + print("starting server configuration") + #_ba.disconnect_client=new_disconnect + settings = setting.get_settings_data() + _ba.set_server_device_name(settings["HostDeviceName"]) + _ba.set_server_name(settings["HostName"]) + _ba.set_transparent_kickvote(settings["ShowKickVoteStarterName"]) + _ba.set_kickvote_msg_type(settings["KickVoteMsgType"]) + + + +def new_disconnect(clid,duration=120): + print("new new_disconnect") + _ba.ban_client(clid,duration) + diff --git a/dist/ba_root/mods/playersData/__pycache__/__init__.cpython-39.pyc b/dist/ba_root/mods/playersData/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..abdab19 Binary files /dev/null and b/dist/ba_root/mods/playersData/__pycache__/__init__.cpython-39.pyc differ diff --git a/dist/ba_root/mods/playersData/__pycache__/pdata.cpython-39.pyc b/dist/ba_root/mods/playersData/__pycache__/pdata.cpython-39.pyc new file mode 100644 index 0000000..2be35d7 Binary files /dev/null and b/dist/ba_root/mods/playersData/__pycache__/pdata.cpython-39.pyc differ diff --git a/dist/ba_root/mods/playersData/pdata.py b/dist/ba_root/mods/playersData/pdata.py index 18b3ec2..da208b3 100644 --- a/dist/ba_root/mods/playersData/pdata.py +++ b/dist/ba_root/mods/playersData/pdata.py @@ -1,6 +1,6 @@ # Released under the MIT License. See LICENSE for details. import _ba, os, json - +from serverData import serverdata roles = {} @@ -13,8 +13,11 @@ data_path = os.path.join(_ba.env()['python_directory_user'],"playersData" + os.s def get_info(id): with open(data_path+'profiles.json', 'r') as f: - profile = json.load(f) - return profile[id] + profiles = json.load(f) + if id in profiles: + + return profiles[id] + return None def get_profiles(): @@ -23,28 +26,36 @@ def get_profiles(): profiles = json.load(f) return profiles def commit_profiles(profiles): - with open(data_path+'profiles.json', 'r') as f: + with open(data_path+'profiles.json', 'w') as f: json.dump(profiles,f,indent=4) -def add_profile(id,display_string,allprofiles,currentname): +def add_profile(id,display_string,currentname,age): f=open(data_path+"profiles.json","r") - profiles=json.load(f.read()) + profiles=json.load(f) f.close() + profiles[id]={"display_string":display_string, + "profiles":[], + "name":currentname, + "isBan":False, + "isMuted":False, + "accountAge":age, + "totaltimeplayer":0, + "lastseen":0} - profiles[id]['display_string']=[display_string] - profiles[id]['profiles']=allprofiles - profiles[id]['name']=currentname - profiles[id]['isBan']=False, - profiles[id]['isMuted']=False, - profiles[id]['totaltimeplayer']=0, - profiles[id]['lastseen']=0, + f=open(data_path+"profiles.json","w") json.dump(profiles,f,indent=4) + serverdata.clients[id]=profiles[id] f.close() +def update_displayString(id,display_string): + profiles=get_profiles() + if id in profiles: + profiles[id]["display_string"]=display_string + commit_profiles(profiles) def update_profile(id,display_string=None,allprofiles=[],name=None): @@ -67,9 +78,7 @@ def update_profile(id,display_string=None,allprofiles=[],name=None): f.close() def ban_player(id): - f=open(data_path+"profiles.json","r") - profiles=json.load(f.read()) - f.close() + profiles= get_profiles() if id in profiles: profiles[id]['isBan']=True commit_profiles(profiles) diff --git a/dist/ba_root/mods/playersData/profiles.json b/dist/ba_root/mods/playersData/profiles.json index ed5be62..283219d 100644 --- a/dist/ba_root/mods/playersData/profiles.json +++ b/dist/ba_root/mods/playersData/profiles.json @@ -1,11 +1,61 @@ { - "pb-difsdf":{ - "display_string":[], - "profiles":[], - "name":"something", - "isBan":false, - "isMuted":false, - "lastseen":14677, - "totaltimeplayed":0, - } + "pb-IF4TVWwZUQ=9=": { + "display_string": "\ue030PC295588", + "profiles": [], + "name": "\ue030PC295588", + "isBan": false, + "isMuted": false, + "totaltimeplayer": 0, + "lastseen": 0 + }, + "pb-IF4TVWwZUQ=d=": { + "display_string": [ + "\ue030Android48444292", + "\ue030PC295588" + ], + "profiles": [], + "name": "\ue030PC295588", + "isBan": false, + "isMuted": false, + "accountAge": 8231.662564509445, + "totaltimeplayer": 0, + "lastseen": 0 + }, + "pb-IF4TVWwZUQ==": { + "display_string": [ + "\ue030Android48444292", + "\ue030PC295588" + ], + "profiles": [], + "name": "\ue030PC295588", + "isBan": false, + "isMuted": false, + "accountAge": 8231.7627117525, + "totaltimeplayer": 0, + "lastseen": 0 + }, + "pb-IF4oUmwDFQ==": { + "display_string": [ + "\ue030PC401824" + ], + "profiles": [], + "name": "\ue030PC401824", + "isBan": false, + "isMuted": false, + "accountAge": 0.15915736722222223, + "totaltimeplayer": 0, + "lastseen": 0 + }, + "pb-IF4dUmwsIg==": { + "display_string": [ + "\ue030PC401877" + ], + "profiles": [], + "name": "\ue030PC401877", + "isBan": false, + "isMuted": false, + "accountAge": 0.01221103722222222, + "totaltimeplayer": 0, + "lastseen": 0 + } } \ No newline at end of file diff --git a/dist/ba_root/mods/serverData/__pycache__/__init__.cpython-39.pyc b/dist/ba_root/mods/serverData/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..b3da0d3 Binary files /dev/null and b/dist/ba_root/mods/serverData/__pycache__/__init__.cpython-39.pyc differ diff --git a/dist/ba_root/mods/serverData/__pycache__/serverdata.cpython-39.pyc b/dist/ba_root/mods/serverData/__pycache__/serverdata.cpython-39.pyc new file mode 100644 index 0000000..2f60cee Binary files /dev/null and b/dist/ba_root/mods/serverData/__pycache__/serverdata.cpython-39.pyc differ diff --git a/dist/ba_root/mods/serverData/serverdata.py b/dist/ba_root/mods/serverData/serverdata.py index 7f31546..97e686a 100644 --- a/dist/ba_root/mods/serverData/serverdata.py +++ b/dist/ba_root/mods/serverData/serverdata.py @@ -1,4 +1,4 @@ # Released under the MIT License. See LICENSE for details. -currentclients=[] +clients={} cachedclients=[] \ No newline at end of file diff --git a/dist/ba_root/mods/setting.json b/dist/ba_root/mods/setting.json index 5e250b6..3f0ae8d 100644 --- a/dist/ba_root/mods/setting.json +++ b/dist/ba_root/mods/setting.json @@ -4,15 +4,15 @@ "spectators": false, "lobbychecktime": 1 }, - - - + + + "ChatCommands": { "BrodcastCommand": true }, - - - + + + "textonmap": { "top watermark": "Welcome to server \n ip 192.168.0.1", "bottom left watermark": "Search Hey Smoothy on Youtube", @@ -22,9 +22,12 @@ "message 3" ] }, - - - + "HostDeviceName":"v1.3.1", + "HostName":"BCS", + "ShowKickVoteStarterName":true, + "KickVoteMsgType":"chat", + "minAgeToChatInHours":78, + "minAgeToJoinInHours":48, "enabletags": true, "enablehptag": true, "enablerank": true, @@ -32,4 +35,4 @@ "enableHitTexts": true, "enableeffects": true, "enableTop5effects": true -} \ No newline at end of file +} diff --git a/dist/ba_root/mods/setting.py b/dist/ba_root/mods/setting.py index 01f21c3..054e82c 100644 --- a/dist/ba_root/mods/setting.py +++ b/dist/ba_root/mods/setting.py @@ -3,13 +3,20 @@ import _ba, json settings_path = _ba.env()["python_directory_user"]+"/setting.json" +settings=None def get_settings_data(): - with open(settings_path, "r") as f: - data = json.load(f) - return data + global settings + if settings==None: + with open(settings_path, "r") as f: + data = json.load(f) + settings=data + return settings + else: + + return settings diff --git a/dist/ba_root/mods/spazmod/__pycache__/effects.cpython-39.pyc b/dist/ba_root/mods/spazmod/__pycache__/effects.cpython-39.pyc new file mode 100644 index 0000000..e18019c Binary files /dev/null and b/dist/ba_root/mods/spazmod/__pycache__/effects.cpython-39.pyc differ diff --git a/dist/ba_root/mods/spazmod/__pycache__/modifyspaz.cpython-39.pyc b/dist/ba_root/mods/spazmod/__pycache__/modifyspaz.cpython-39.pyc new file mode 100644 index 0000000..f8c494a Binary files /dev/null and b/dist/ba_root/mods/spazmod/__pycache__/modifyspaz.cpython-39.pyc differ diff --git a/dist/ba_root/mods/spazmod/__pycache__/tag.cpython-39.pyc b/dist/ba_root/mods/spazmod/__pycache__/tag.cpython-39.pyc new file mode 100644 index 0000000..03ec095 Binary files /dev/null and b/dist/ba_root/mods/spazmod/__pycache__/tag.cpython-39.pyc differ diff --git a/dist/ba_root/mods/spazmod/effects.py b/dist/ba_root/mods/spazmod/effects.py index 10f3d36..e8910db 100644 --- a/dist/ba_root/mods/spazmod/effects.py +++ b/dist/ba_root/mods/spazmod/effects.py @@ -21,7 +21,7 @@ from stats import mystats from tools import globalvars as gvar PlayerType = TypeVar('PlayerType', bound=ba.Player) TeamType = TypeVar('TeamType', bound=ba.Team) -from ba._enums import TimeType +from ba._generated.enums import TimeType tt = ba.TimeType.SIM tf = ba.TimeFormat.MILLISECONDS @@ -141,7 +141,7 @@ class Effect(ba.Actor): self.checkDeadTimer = None self._hasDead = False self.light = None - + node_id = self.source_player.node.playerID cl_str = None clID = None @@ -150,13 +150,13 @@ class Effect(ba.Actor): profiles = c.inputdevice.get_player_profiles() clID = c.inputdevice.client_id cl_str = c.get_account_id() - + try: if cl_str in custom_effects: effect = custom_effects[cl_str] - + if effect == 'ice': - + self.emitIce() self.snowTimer = ba.Timer(0.5, self.emitIce, repeat=True, timetype=TimeType.SIM) return @@ -194,19 +194,19 @@ class Effect(ba.Actor): rank = pats[cl_str]["rank"] if rank < 6: if rank == 1: - + self.surround = SurroundBall(spaz, shape="bones") #self.neroLightTimer = ba.Timer(500, ba.WeakCall(self.neonLightSwitch,("shine" in self.Decorations),("extra_Highlight" in self.Decorations),("extra_NameColor" in self.Decorations)),repeat = True, timetype=tt, timeformat=tf) elif rank == 2: - + self.smokeTimer = ba.Timer(40, self.emitSmoke, repeat=True, timetype=tt, timeformat=tf) elif rank == 3: - + self.addLightColor((1, 0.6, 0.4));self.scorchTimer = ba.Timer(500, self.update_Scorch, repeat=True, timetype=tt, timeformat=tf) elif rank == 4: - + self.metalTimer = ba.Timer(500, self.emitMetal, repeat=True, timetype=tt, timeformat=tf) else: - + self.addLightColor((1, 0.6, 0.4));self.checkDeadTimer = ba.Timer(150, self.checkPlayerifDead, repeat=True, timetype=tt, timeformat=tf) if "smoke" and "spark" and "snowDrops" and "slimeDrops" and "metalDrops" and "Distortion" and "neroLight" and "scorch" and "HealTimer" and "KamikazeCheck" not in self.Decorations: @@ -277,7 +277,7 @@ class Effect(ba.Actor): def emitIce(self): spaz = self.spazRef() - + if spaz is None or not spaz.is_alive() or not spaz.node.exists(): self.handlemessage(ba.DieMessage()) return diff --git a/dist/ba_root/mods/stats/__pycache__/__init__.cpython-39.pyc b/dist/ba_root/mods/stats/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..13410c5 Binary files /dev/null and b/dist/ba_root/mods/stats/__pycache__/__init__.cpython-39.pyc differ diff --git a/dist/ba_root/mods/stats/__pycache__/mystats.cpython-39.pyc b/dist/ba_root/mods/stats/__pycache__/mystats.cpython-39.pyc new file mode 100644 index 0000000..3cbeacf Binary files /dev/null and b/dist/ba_root/mods/stats/__pycache__/mystats.cpython-39.pyc differ diff --git a/dist/ba_root/mods/stats/mystats.py b/dist/ba_root/mods/stats/mystats.py index c9aaa39..48841bc 100644 --- a/dist/ba_root/mods/stats/mystats.py +++ b/dist/ba_root/mods/stats/mystats.py @@ -8,7 +8,6 @@ ranks=[] import threading,json,os,urllib.request,ba,_ba,setting from ba._activity import Activity from ba._music import setmusic, MusicType -from ba._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 @@ -65,14 +64,14 @@ def get_stats_by_id(ID: str): if ID in a: return a[ID] else: - + return None def refreshStats(): # lastly, write a pretty html version. # our stats url could point at something like this... pStats = get_all_stats() - f=open(htmlfile, 'w') + f=open(htmlfile, 'w') f.write(html_start) entries = [(a['scores'], a['kills'], a['deaths'], a['games'], a['name'], a['aid']) for a in pStats.values()] # this gives us a list of kills/names sorted high-to-low @@ -142,7 +141,7 @@ def refreshStats(): ranks=_ranks dump_stats(pStats) - + from playersData import pdata pdata.update_toppers(toppersIDs) @@ -151,7 +150,7 @@ def update(score_set): Given a Session's ScoreSet, tallies per-account kills and passes them to a background thread to process and store. - """ + """ # look at score-set entries to tally per-account kills for this round account_kills = {} @@ -172,7 +171,7 @@ def update(score_set): # from disk, do display-string lookups for accounts that need them, # and write everything back to disk (along with a pretty html version) # We use a background thread so our server doesn't hitch while doing this. - + if account_scores: UpdateThread(account_kills, account_deaths, account_scores).start() @@ -182,10 +181,10 @@ class UpdateThread(threading.Thread): self._account_kills = account_kills self.account_deaths = account_deaths self.account_scores = account_scores - + def run(self): # pull our existing stats from disk - + try: if os.path.exists(statsfile): with open(statsfile) as f: @@ -245,4 +244,4 @@ def getRank(acc_id): if ranks==[]: refreshStats() if acc_id in ranks: - return ranks.index(acc_id)+1 \ No newline at end of file + return ranks.index(acc_id)+1 diff --git a/dist/ba_root/mods/stats/stats.json b/dist/ba_root/mods/stats/stats.json index 4b6bbf4..59b09fa 100644 --- a/dist/ba_root/mods/stats/stats.json +++ b/dist/ba_root/mods/stats/stats.json @@ -14,13 +14,13 @@ "pb-IF4TVWwZUQ==": { "rank": 4, "name": "\ue030PC295588", - "scores": 546, + "scores": 610, "total_damage": 0.0, "kills": 1, "deaths": 73, - "games": 47, + "games": 54, "kd": 0.013, - "avg_score": 11.617, + "avg_score": 11.296, "aid": "pb-IF4TVWwZUQ==" }, "pb-JiNJARBaXEFBVF9HFkNXXF1EF0ZaRlZE": { diff --git a/dist/ba_root/mods/stats/stats_page.html b/dist/ba_root/mods/stats/stats_page.html index 4392c07..4b4fd26 100644 --- a/dist/ba_root/mods/stats/stats_page.html +++ b/dist/ba_root/mods/stats/stats_page.html @@ -44,10 +44,10 @@ 4 PC295588 - 546 + 610 1 73 - 47 + 54 5 diff --git a/dist/ba_root/mods/tools/__pycache__/Logger.cpython-39.pyc b/dist/ba_root/mods/tools/__pycache__/Logger.cpython-39.pyc new file mode 100644 index 0000000..fddadc8 Binary files /dev/null and b/dist/ba_root/mods/tools/__pycache__/Logger.cpython-39.pyc differ diff --git a/dist/ba_root/mods/tools/__pycache__/__init__.cpython-39.pyc b/dist/ba_root/mods/tools/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..6849df2 Binary files /dev/null and b/dist/ba_root/mods/tools/__pycache__/__init__.cpython-39.pyc differ diff --git a/dist/ba_root/mods/tools/__pycache__/globalvars.cpython-39.pyc b/dist/ba_root/mods/tools/__pycache__/globalvars.cpython-39.pyc new file mode 100644 index 0000000..f620a5e Binary files /dev/null and b/dist/ba_root/mods/tools/__pycache__/globalvars.cpython-39.pyc differ diff --git a/dist/ba_root/mods/tools/__pycache__/servercheck.cpython-39.pyc b/dist/ba_root/mods/tools/__pycache__/servercheck.cpython-39.pyc new file mode 100644 index 0000000..5f578c1 Binary files /dev/null and b/dist/ba_root/mods/tools/__pycache__/servercheck.cpython-39.pyc differ diff --git a/dist/ba_root/mods/tools/__pycache__/textonmap.cpython-39.pyc b/dist/ba_root/mods/tools/__pycache__/textonmap.cpython-39.pyc new file mode 100644 index 0000000..ed42b83 Binary files /dev/null and b/dist/ba_root/mods/tools/__pycache__/textonmap.cpython-39.pyc differ diff --git a/dist/ba_root/mods/tools/__pycache__/whitelist.cpython-39.pyc b/dist/ba_root/mods/tools/__pycache__/whitelist.cpython-39.pyc new file mode 100644 index 0000000..8c558f3 Binary files /dev/null and b/dist/ba_root/mods/tools/__pycache__/whitelist.cpython-39.pyc differ diff --git a/dist/ba_root/mods/tools/servercheck.py b/dist/ba_root/mods/tools/servercheck.py index 70007fb..128963f 100644 --- a/dist/ba_root/mods/tools/servercheck.py +++ b/dist/ba_root/mods/tools/servercheck.py @@ -4,56 +4,217 @@ # NOT COMPLETED YET from serverData import serverdata +from playersData import pdata +import _ba +import urllib.request +import json +import datetime +import time +import ba +from ba._general import Call +import threading +import setting +# class ServerChecker: + +# def __init__(): +# run() + +# def run(self): +# clients=roset.players +# # check if some one joined the party +# for client in clients: +# if cleint.account_id not in serverdata.currentclients: +# self.playerjoined(client) +# # check if some one left the party +# clients_id=[client.account_id for client in clients] +# for player in serverdata.currentclients: +# if player not in clients_id: +# self.playerleft(player) -class ServerChecker: +# def playerjoined(self,client): +# if client.account_id in serverdata.cachedclients: +# serevrdata.currentclients[client_account_id]=serverdata.cachedclients[id] - def __init__(): - run() - - def run(self): - clients=roset.players - # check if some one joined the party - for client in clients: - if cleint.account_id not in serverdata.currentclients: - self.playerjoined(client) - # check if some one left the party - clients_id=[client.account_id for client in clients] - for player in serverdata.currentclients: - if player not in clients_id: - self.playerleft(player) +# playerData=pdata.get_info(client.account_id) +# playerData["lastjoin"]=time.time() +# if playerData ==None: +# self.registernewplayer(cleint) +# else if playerData['isBan']: +# _ba.disconnect_client(client.client_id,9999) +# else: +# serverData.currentclients[client_account_id]=playerData - def playerjoined(self,client): - if client.account_id in serverdata.cachedclients: - serevrdata.currentclients[client_account_id]=serverdata.cachedclients[id] +# def playerleft(self,player): +# serverdata.cachedclients[player]=serverdata.currentclients[player] - playerData=pdata.get_info(client.account_id) - playerData["lastjoin"]=time.time() - if playerData ==None: - self.registernewplayer(cleint) - else if playerData['isBan']: - _ba.disconnect_client(client.client_id,9999) +# serverdata.currentclients.remove(player) + +# timeplayed=time.time()-serverdata.currentclients[player]['lastjoin'] +# serverdata.cachedclients[player]["totaltimeplayed"]+=timeplayed + +# pdata.update_profile(serverdata.cachedclients[player]) + +settings = setting.get_settings_data() + +def on_player_join(pbid): + + player_data=pdata.get_info(pbid) + + if player_data!=None: + device_strin="" + if player_data["isBan"] or player_data["accountAge"] < settings["minAgeToJoinInHours"]: + for ros in _ba.get_game_roster(): + if ros['account_id']==pbid: + if not player_data["isBan"]: + _ba.screenmessage("New Accounts not allowed here , come back later",transient=True,clients=[ros['client_id']]) + _ba.disconnect_client(ros['client_id']) + return else: - serverData.currentclients[client_account_id]=playerData - - - def playerleft(self,player): - serverdata.cachedclients[player]=serverdata.currentclients[player] - - serverdata.currentclients.remove(player) - - timeplayed=time.time()-serverdata.currentclients[player]['lastjoin'] - serverdata.cachedclients[player]["totaltimeplayed"]+=timeplayed - - pdata.update_profile(serverdata.cachedclients[player]) - - + + serverdata.clients[pbid]=player_data + + verify_account(pbid,player_data) + + else: + + d_string="" + for ros in _ba.get_game_roster(): + if ros['account_id']==pbid: + d_string=ros['display_string'] + + thread = FetchThread( + target=my_acc_age, + callback=save_age, + pb_id=pbid, + display_string=d_string + ) + + thread.start() + + + + #pdata.add_profile(pbid,d_string,d_string) + +def verify_account(pb_id,p_data): + d_string="" + for ros in _ba.get_game_roster(): + if ros['account_id']==pb_id: + d_string=ros['display_string'] + + if d_string not in p_data['display_string']: + + thread2 = FetchThread( + target=get_device_accounts, + callback=save_ids, + pb_id=pb_id, + display_string=d_string + ) + thread2.start() +#============== IGNORE BLOW CODE , ELSE DIE ======================= +def _make_request_safe(request, retries=2, raise_err=True): + try: + return request() + except: + if retries > 0: + time.sleep(1) + return _make_request_safe(request, retries=retries-1, raise_err=raise_err) + if raise_err: + raise +def get_account_age_in_hours(pb_id): + # thanks rikko + account_creation_url = "http://bombsquadgame.com/accountquery?id=" + pb_id + account_creation = _make_request_safe(lambda: urllib.request.urlopen(account_creation_url)) + if account_creation is not None: + try: + account_creation = json.loads(account_creation.read()) + except ValueError: + pass + else: + creation_time = account_creation["created"] + creation_time = map(str, creation_time) + creation_time = datetime.datetime.strptime("/".join(creation_time), "%Y/%m/%d/%H/%M/%S") + # Convert to IST + creation_time += datetime.timedelta(hours=5, minutes=30) + print(creation_time) + now = datetime.datetime.now() + delta = now - creation_time + delta_hours = delta.total_seconds() / (60 * 60) + return delta_hours +def get_device_accounts(pb_id): + url="http://bombsquadgame.com/bsAccountInfo?buildNumber=20258&accountID="+pb_id + data=_make_request_safe(lambda:urllib.request.urlopen(url)) + if data is not None: + try: + accounts=json.loads(data.read())["accountDisplayStrings"] + except ValueError: + return ['???'] + else: + return accounts + +# ======= yes fucking threading code , dont touch ============== + + +class FetchThread(threading.Thread): + def __init__(self,target, callback=None,pb_id="ji",display_string="XXX"): + + super(FetchThread, self).__init__(target=self.target_with_callback, args=(pb_id,display_string,)) + self.callback = callback + self.method = target + + + def target_with_callback(self,pb_id,display_string): + + data=self.method(pb_id) + if self.callback is not None: + self.callback(data,pb_id,display_string) + + +def my_acc_age(pb_id): + + return get_account_age_in_hours(pb_id) + + +def save_age(age, pb_id,display_string): + + + pdata.add_profile(pb_id,display_string,display_string,age) + time.sleep(2) + thread2 = FetchThread( + target=get_device_accounts, + callback=save_ids, + pb_id=pb_id, + display_string=display_string + ) + thread2.start() + if age < settings["minAgeToJoinInHours"]: + msg="New Accounts not allowed to play here , come back tmrw." + _ba.pushcall(Call(kick_by_pb_id,pb_id,msg),from_other_thread=True) + +def save_ids(ids,pb_id,display_string): + + + pdata.update_displayString(pb_id,ids) + + if display_string not in ids: + msg="Spoofed Id detected , Goodbye" + _ba.pushcall(Call(kick_by_pb_id,pb_id,msg),from_other_thread=True) + + + +def kick_by_pb_id(pb_id,msg): + for ros in _ba.get_game_roster(): + if ros['account_id']==pb_id: + _ba.screenmessage(msg, transient=True, clients=[ros['client_id']]) + _ba.disconnect_client(ros['client_id']) + _ba.chatmessage("id spoofer kicked") + diff --git a/dist/ba_root/mods/tools/textonmap.py b/dist/ba_root/mods/tools/textonmap.py index 1664bd2..c5c493d 100644 --- a/dist/ba_root/mods/tools/textonmap.py +++ b/dist/ba_root/mods/tools/textonmap.py @@ -2,18 +2,18 @@ """ TODO need to set coordinates of text node , move timer values to settings.json """ -from ba._enums import TimeType +from ba._generated.enums import TimeType import ba, _ba import setting class textonmap: - + def __init__(self): - + data = setting.get_settings_data()['textonmap'] left = data['bottom left watermark'] top = data['top watermark'] - + self.index = 0 self.highlights = data['center highlights'] self.left_watermark(left) @@ -31,10 +31,10 @@ class textonmap: 'position':(0,138), 'color':(1,1,1) }) - + self.delt = ba.timer(7,node.delete) self.index = int((self.index+1)%len(self.highlights)) - + def left_watermark(self, text): node = _ba.newnode('text', attrs={ @@ -46,7 +46,7 @@ class textonmap: 'position':(-480,20), 'color':(1,1,1) }) - + def top_message(self, text): node = _ba.newnode('text', attrs={ @@ -58,4 +58,3 @@ class textonmap: 'position':(0,138), 'color':(1,1,1) }) - \ No newline at end of file diff --git a/dist/ba_root/mods/tools/whitelist.py b/dist/ba_root/mods/tools/whitelist.py index 354ae92..2032ae3 100644 --- a/dist/ba_root/mods/tools/whitelist.py +++ b/dist/ba_root/mods/tools/whitelist.py @@ -1,142 +1,142 @@ -""" -Private Server whitelist by Mr.Smoothy - -* don't dare to remove credits or I will bite you - -GitHub : https://github.com/imayushsaini/Bombsquad-Ballistica-Modded-Server -""" -# Released under the MIT License. See LICENSE for details. - - -# ba_meta require api 6 -from __future__ import annotations -from typing import TYPE_CHECKING -from ba._enums import TimeType - -import ba, _ba, time, json, datetime, setting - -if TYPE_CHECKING: - pass - - -whitelist={} - - - -whitelistFile = _ba.env()["python_directory_user"]+"/tools/whitelist.json" -logs_path = _ba.env()["python_directory_user"]+"/serverData/wl_logs.txt" - - -def commit(data): - with open(whitelistFile, "w") as f: - json.dump(data, f, indent=4) - - -def add_commit_to_logs(commit : str): - with open(logs_path, "a") as f: - f.write(commit+"\n") - - -def get_whitelist_data(): - global whitelist - if whitelist != {}: - return whitelist - try: - with open(whitelistFile, "r") as f: - data = json.load(f) - whitelist=data - except: - print("No Whitelist Detected , Creating One") - whitelist={} - whitelist['pb-JiNJARBaXEFBVF9HFkNXXF1EF0ZaRlZE']=['smoothyki-id','mr.smoothy'] - commit(whitelist) - - return whitelist - - -def in_white_list(accountid : str): - data = get_whitelist_data() - - if str(accountid) in data: - return True - else: - return False - - -def add_to_white_list(accountid : str, display_string : str): - data = get_whitelist_data() - - if accountid not in data: - data[str(accountid)] = [str(display_string)] - - else: - data[str(accountid)].append(str(display_string)) - - commit(data) - - -def handle_player_request(player): - data = get_whitelist_data() - settings = setting.get_settings_data()["white_list"] - accountid = player.get_account_id() - - if settings["whitelist_on"]: - if in_white_list(accountid): - return - else: - rost = _ba.get_game_roster() - - for i in rost: - if i["account_id"] == accountid: - _ba.disconnect_client(int(i['client_id'])) - - -def display_string_in_white_list(display_string : str): - return any(display_string in i for i in data.values()) - - -class Whitelist: - def __init__(self): - global whitelist - - settings = setting.get_settings_data()["white_list"] - whitelist_on = settings["whitelist_on"] - spectators = settings["spectators"] - lobbychecktime = settings["lobbychecktime"] - # _ba.chatmessage(f"{settings} {whitelist_on} {spectators} {lobbychecktime}") - - - get_whitelist_data() - - - if whitelist_on and not spectators: - self.timer = ba.Timer(lobbychecktime, self.checklobby, repeat=True, timetype=TimeType.REAL) - - def checklobby(self): - global whitelist - settings = setting.get_settings_data()["white_list"] - whitelist_on = settings["whitelist_on"] - spectators = settings["spectators"] - lobbychecktime = settings["lobbychecktime"] - - if whitelist_on and not spectators: - if True: - - rost = _ba.get_game_roster() - for i in rost: - if i['account_id'] in whitelist and i['account_id'] != '' or i['client_id'] == -1: - pass - - else: - try: - add_commit_to_logs("Kicked from lobby "+i['account_id']) - except: - pass - _ba.disconnect_client(i['client_id']) - - # except: - # return - else: - self.timer =None - - +""" +Private Server whitelist by Mr.Smoothy + +* don't dare to remove credits or I will bite you + +GitHub : https://github.com/imayushsaini/Bombsquad-Ballistica-Modded-Server +""" +# Released under the MIT License. See LICENSE for details. + + +# ba_meta require api 6 +from __future__ import annotations +from typing import TYPE_CHECKING +from ba._generated.enums import TimeType + +import ba, _ba, time, json, datetime, setting + +if TYPE_CHECKING: + pass + + +whitelist={} + + + +whitelistFile = _ba.env()["python_directory_user"]+"/tools/whitelist.json" +logs_path = _ba.env()["python_directory_user"]+"/serverData/wl_logs.txt" + + +def commit(data): + with open(whitelistFile, "w") as f: + json.dump(data, f, indent=4) + + +def add_commit_to_logs(commit : str): + with open(logs_path, "a") as f: + f.write(commit+"\n") + + +def get_whitelist_data(): + global whitelist + if whitelist != {}: + return whitelist + try: + with open(whitelistFile, "r") as f: + data = json.load(f) + whitelist=data + except: + print("No Whitelist Detected , Creating One") + whitelist={} + whitelist['pb-JiNJARBaXEFBVF9HFkNXXF1EF0ZaRlZE']=['smoothyki-id','mr.smoothy'] + commit(whitelist) + + return whitelist + + +def in_white_list(accountid : str): + data = get_whitelist_data() + + if str(accountid) in data: + return True + else: + return False + + +def add_to_white_list(accountid : str, display_string : str): + data = get_whitelist_data() + + if accountid not in data: + data[str(accountid)] = [str(display_string)] + + else: + data[str(accountid)].append(str(display_string)) + + commit(data) + + +def handle_player_request(player): + data = get_whitelist_data() + settings = setting.get_settings_data()["white_list"] + accountid = player.get_account_id() + + if settings["whitelist_on"]: + if in_white_list(accountid): + return + else: + rost = _ba.get_game_roster() + + for i in rost: + if i["account_id"] == accountid: + _ba.disconnect_client(int(i['client_id'])) + + +def display_string_in_white_list(display_string : str): + return any(display_string in i for i in data.values()) + + +class Whitelist: + def __init__(self): + global whitelist + + settings = setting.get_settings_data()["white_list"] + whitelist_on = settings["whitelist_on"] + spectators = settings["spectators"] + lobbychecktime = settings["lobbychecktime"] + # _ba.chatmessage(f"{settings} {whitelist_on} {spectators} {lobbychecktime}") + + + get_whitelist_data() + + + if whitelist_on and not spectators: + self.timer = ba.Timer(lobbychecktime, self.checklobby, repeat=True, timetype=TimeType.REAL) + + def checklobby(self): + global whitelist + settings = setting.get_settings_data()["white_list"] + whitelist_on = settings["whitelist_on"] + spectators = settings["spectators"] + lobbychecktime = settings["lobbychecktime"] + + if whitelist_on and not spectators: + if True: + + rost = _ba.get_game_roster() + for i in rost: + if i['account_id'] in whitelist and i['account_id'] != '' or i['client_id'] == -1: + pass + + else: + try: + add_commit_to_logs("Kicked from lobby "+i['account_id']) + except: + pass + _ba.disconnect_client(i['client_id']) + + # except: + # return + else: + self.timer =None + + diff --git a/dist/ballisticacore_headless b/dist/ballisticacore_headless new file mode 100644 index 0000000..71a1de0 Binary files /dev/null and b/dist/ballisticacore_headless differ diff --git a/dist/bombsquad_headless b/dist/bombsquad_headless deleted file mode 100644 index 28f55a3..0000000 Binary files a/dist/bombsquad_headless and /dev/null differ diff --git a/dist/lib/__future__.py b/dist/lib/__future__.py deleted file mode 100644 index d7cb8ac..0000000 --- a/dist/lib/__future__.py +++ /dev/null @@ -1,146 +0,0 @@ -"""Record of phased-in incompatible language changes. - -Each line is of the form: - - FeatureName = "_Feature(" OptionalRelease "," MandatoryRelease "," - CompilerFlag ")" - -where, normally, OptionalRelease < MandatoryRelease, and both are 5-tuples -of the same form as sys.version_info: - - (PY_MAJOR_VERSION, # the 2 in 2.1.0a3; an int - PY_MINOR_VERSION, # the 1; an int - PY_MICRO_VERSION, # the 0; an int - PY_RELEASE_LEVEL, # "alpha", "beta", "candidate" or "final"; string - PY_RELEASE_SERIAL # the 3; an int - ) - -OptionalRelease records the first release in which - - from __future__ import FeatureName - -was accepted. - -In the case of MandatoryReleases that have not yet occurred, -MandatoryRelease predicts the release in which the feature will become part -of the language. - -Else MandatoryRelease records when the feature became part of the language; -in releases at or after that, modules no longer need - - from __future__ import FeatureName - -to use the feature in question, but may continue to use such imports. - -MandatoryRelease may also be None, meaning that a planned feature got -dropped. - -Instances of class _Feature have two corresponding methods, -.getOptionalRelease() and .getMandatoryRelease(). - -CompilerFlag is the (bitfield) flag that should be passed in the fourth -argument to the builtin function compile() to enable the feature in -dynamically compiled code. This flag is stored in the .compiler_flag -attribute on _Future instances. These values must match the appropriate -#defines of CO_xxx flags in Include/compile.h. - -No feature line is ever to be deleted from this file. -""" - -all_feature_names = [ - "nested_scopes", - "generators", - "division", - "absolute_import", - "with_statement", - "print_function", - "unicode_literals", - "barry_as_FLUFL", - "generator_stop", - "annotations", -] - -__all__ = ["all_feature_names"] + all_feature_names - -# The CO_xxx symbols are defined here under the same names defined in -# code.h and used by compile.h, so that an editor search will find them here. -# However, they're not exported in __all__, because they don't really belong to -# this module. -CO_NESTED = 0x0010 # nested_scopes -CO_GENERATOR_ALLOWED = 0 # generators (obsolete, was 0x1000) -CO_FUTURE_DIVISION = 0x20000 # division -CO_FUTURE_ABSOLUTE_IMPORT = 0x40000 # perform absolute imports by default -CO_FUTURE_WITH_STATEMENT = 0x80000 # with statement -CO_FUTURE_PRINT_FUNCTION = 0x100000 # print function -CO_FUTURE_UNICODE_LITERALS = 0x200000 # unicode string literals -CO_FUTURE_BARRY_AS_BDFL = 0x400000 -CO_FUTURE_GENERATOR_STOP = 0x800000 # StopIteration becomes RuntimeError in generators -CO_FUTURE_ANNOTATIONS = 0x1000000 # annotations become strings at runtime - -class _Feature: - def __init__(self, optionalRelease, mandatoryRelease, compiler_flag): - self.optional = optionalRelease - self.mandatory = mandatoryRelease - self.compiler_flag = compiler_flag - - def getOptionalRelease(self): - """Return first release in which this feature was recognized. - - This is a 5-tuple, of the same form as sys.version_info. - """ - - return self.optional - - def getMandatoryRelease(self): - """Return release in which this feature will become mandatory. - - This is a 5-tuple, of the same form as sys.version_info, or, if - the feature was dropped, is None. - """ - - return self.mandatory - - def __repr__(self): - return "_Feature" + repr((self.optional, - self.mandatory, - self.compiler_flag)) - -nested_scopes = _Feature((2, 1, 0, "beta", 1), - (2, 2, 0, "alpha", 0), - CO_NESTED) - -generators = _Feature((2, 2, 0, "alpha", 1), - (2, 3, 0, "final", 0), - CO_GENERATOR_ALLOWED) - -division = _Feature((2, 2, 0, "alpha", 2), - (3, 0, 0, "alpha", 0), - CO_FUTURE_DIVISION) - -absolute_import = _Feature((2, 5, 0, "alpha", 1), - (3, 0, 0, "alpha", 0), - CO_FUTURE_ABSOLUTE_IMPORT) - -with_statement = _Feature((2, 5, 0, "alpha", 1), - (2, 6, 0, "alpha", 0), - CO_FUTURE_WITH_STATEMENT) - -print_function = _Feature((2, 6, 0, "alpha", 2), - (3, 0, 0, "alpha", 0), - CO_FUTURE_PRINT_FUNCTION) - -unicode_literals = _Feature((2, 6, 0, "alpha", 2), - (3, 0, 0, "alpha", 0), - CO_FUTURE_UNICODE_LITERALS) - -barry_as_FLUFL = _Feature((3, 1, 0, "alpha", 2), - (4, 0, 0, "alpha", 0), - CO_FUTURE_BARRY_AS_BDFL) - -generator_stop = _Feature((3, 5, 0, "beta", 1), - (3, 7, 0, "alpha", 0), - CO_FUTURE_GENERATOR_STOP) - -annotations = _Feature((3, 7, 0, "beta", 1), - (4, 0, 0, "alpha", 0), - CO_FUTURE_ANNOTATIONS) diff --git a/dist/lib/__phello__.foo.py b/dist/lib/__phello__.foo.py deleted file mode 100644 index 8e8623e..0000000 --- a/dist/lib/__phello__.foo.py +++ /dev/null @@ -1 +0,0 @@ -# This file exists as a helper for the test.test_frozen module. diff --git a/dist/lib/__pycache__/__future__.cpython-38.opt-1.pyc b/dist/lib/__pycache__/__future__.cpython-38.opt-1.pyc deleted file mode 100644 index 0bf191f..0000000 Binary files a/dist/lib/__pycache__/__future__.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/__phello__.foo.cpython-38.opt-1.pyc b/dist/lib/__pycache__/__phello__.foo.cpython-38.opt-1.pyc deleted file mode 100644 index 8a3a4d0..0000000 Binary files a/dist/lib/__pycache__/__phello__.foo.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/_bootlocale.cpython-38.opt-1.pyc b/dist/lib/__pycache__/_bootlocale.cpython-38.opt-1.pyc deleted file mode 100644 index cebdf12..0000000 Binary files a/dist/lib/__pycache__/_bootlocale.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/_collections_abc.cpython-38.opt-1.pyc b/dist/lib/__pycache__/_collections_abc.cpython-38.opt-1.pyc deleted file mode 100644 index c4ab11e..0000000 Binary files a/dist/lib/__pycache__/_collections_abc.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/_compat_pickle.cpython-38.opt-1.pyc b/dist/lib/__pycache__/_compat_pickle.cpython-38.opt-1.pyc deleted file mode 100644 index 366dbcf..0000000 Binary files a/dist/lib/__pycache__/_compat_pickle.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/_compression.cpython-38.opt-1.pyc b/dist/lib/__pycache__/_compression.cpython-38.opt-1.pyc deleted file mode 100644 index 34c66c7..0000000 Binary files a/dist/lib/__pycache__/_compression.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/_dummy_thread.cpython-38.opt-1.pyc b/dist/lib/__pycache__/_dummy_thread.cpython-38.opt-1.pyc deleted file mode 100644 index 2a34e23..0000000 Binary files a/dist/lib/__pycache__/_dummy_thread.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/_markupbase.cpython-38.opt-1.pyc b/dist/lib/__pycache__/_markupbase.cpython-38.opt-1.pyc deleted file mode 100644 index 183da10..0000000 Binary files a/dist/lib/__pycache__/_markupbase.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/_osx_support.cpython-38.opt-1.pyc b/dist/lib/__pycache__/_osx_support.cpython-38.opt-1.pyc deleted file mode 100644 index e799447..0000000 Binary files a/dist/lib/__pycache__/_osx_support.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/_py_abc.cpython-38.opt-1.pyc b/dist/lib/__pycache__/_py_abc.cpython-38.opt-1.pyc deleted file mode 100644 index 342bc17..0000000 Binary files a/dist/lib/__pycache__/_py_abc.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/_pydecimal.cpython-38.opt-1.pyc b/dist/lib/__pycache__/_pydecimal.cpython-38.opt-1.pyc deleted file mode 100644 index 0e850d0..0000000 Binary files a/dist/lib/__pycache__/_pydecimal.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/_pyio.cpython-38.opt-1.pyc b/dist/lib/__pycache__/_pyio.cpython-38.opt-1.pyc deleted file mode 100644 index 893e941..0000000 Binary files a/dist/lib/__pycache__/_pyio.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/_sitebuiltins.cpython-38.opt-1.pyc b/dist/lib/__pycache__/_sitebuiltins.cpython-38.opt-1.pyc deleted file mode 100644 index f25a897..0000000 Binary files a/dist/lib/__pycache__/_sitebuiltins.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/_strptime.cpython-38.opt-1.pyc b/dist/lib/__pycache__/_strptime.cpython-38.opt-1.pyc deleted file mode 100644 index 7df5354..0000000 Binary files a/dist/lib/__pycache__/_strptime.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/_threading_local.cpython-38.opt-1.pyc b/dist/lib/__pycache__/_threading_local.cpython-38.opt-1.pyc deleted file mode 100644 index 800274b..0000000 Binary files a/dist/lib/__pycache__/_threading_local.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/_weakrefset.cpython-38.opt-1.pyc b/dist/lib/__pycache__/_weakrefset.cpython-38.opt-1.pyc deleted file mode 100644 index 6081f3c..0000000 Binary files a/dist/lib/__pycache__/_weakrefset.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/abc.cpython-38.opt-1.pyc b/dist/lib/__pycache__/abc.cpython-38.opt-1.pyc deleted file mode 100644 index 65cfe4a..0000000 Binary files a/dist/lib/__pycache__/abc.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/aifc.cpython-38.opt-1.pyc b/dist/lib/__pycache__/aifc.cpython-38.opt-1.pyc deleted file mode 100644 index 35a0eb7..0000000 Binary files a/dist/lib/__pycache__/aifc.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/antigravity.cpython-38.opt-1.pyc b/dist/lib/__pycache__/antigravity.cpython-38.opt-1.pyc deleted file mode 100644 index aa15f5d..0000000 Binary files a/dist/lib/__pycache__/antigravity.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/argparse.cpython-38.opt-1.pyc b/dist/lib/__pycache__/argparse.cpython-38.opt-1.pyc deleted file mode 100644 index 78d643c..0000000 Binary files a/dist/lib/__pycache__/argparse.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/ast.cpython-38.opt-1.pyc b/dist/lib/__pycache__/ast.cpython-38.opt-1.pyc deleted file mode 100644 index 2df5a49..0000000 Binary files a/dist/lib/__pycache__/ast.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/asynchat.cpython-38.opt-1.pyc b/dist/lib/__pycache__/asynchat.cpython-38.opt-1.pyc deleted file mode 100644 index 2175586..0000000 Binary files a/dist/lib/__pycache__/asynchat.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/asyncore.cpython-38.opt-1.pyc b/dist/lib/__pycache__/asyncore.cpython-38.opt-1.pyc deleted file mode 100644 index c127ff3..0000000 Binary files a/dist/lib/__pycache__/asyncore.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/base64.cpython-38.opt-1.pyc b/dist/lib/__pycache__/base64.cpython-38.opt-1.pyc deleted file mode 100644 index bbcbdda..0000000 Binary files a/dist/lib/__pycache__/base64.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/bdb.cpython-38.opt-1.pyc b/dist/lib/__pycache__/bdb.cpython-38.opt-1.pyc deleted file mode 100644 index edabe3f..0000000 Binary files a/dist/lib/__pycache__/bdb.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/binhex.cpython-38.opt-1.pyc b/dist/lib/__pycache__/binhex.cpython-38.opt-1.pyc deleted file mode 100644 index c7b75cb..0000000 Binary files a/dist/lib/__pycache__/binhex.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/bisect.cpython-38.opt-1.pyc b/dist/lib/__pycache__/bisect.cpython-38.opt-1.pyc deleted file mode 100644 index 8eac1e9..0000000 Binary files a/dist/lib/__pycache__/bisect.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/bz2.cpython-38.opt-1.pyc b/dist/lib/__pycache__/bz2.cpython-38.opt-1.pyc deleted file mode 100644 index 407ad09..0000000 Binary files a/dist/lib/__pycache__/bz2.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/cProfile.cpython-38.opt-1.pyc b/dist/lib/__pycache__/cProfile.cpython-38.opt-1.pyc deleted file mode 100644 index ed37fd5..0000000 Binary files a/dist/lib/__pycache__/cProfile.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/calendar.cpython-38.opt-1.pyc b/dist/lib/__pycache__/calendar.cpython-38.opt-1.pyc deleted file mode 100644 index 4210284..0000000 Binary files a/dist/lib/__pycache__/calendar.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/cgi.cpython-38.opt-1.pyc b/dist/lib/__pycache__/cgi.cpython-38.opt-1.pyc deleted file mode 100644 index 8ba6720..0000000 Binary files a/dist/lib/__pycache__/cgi.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/cgitb.cpython-38.opt-1.pyc b/dist/lib/__pycache__/cgitb.cpython-38.opt-1.pyc deleted file mode 100644 index d0371cf..0000000 Binary files a/dist/lib/__pycache__/cgitb.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/chunk.cpython-38.opt-1.pyc b/dist/lib/__pycache__/chunk.cpython-38.opt-1.pyc deleted file mode 100644 index b77b75a..0000000 Binary files a/dist/lib/__pycache__/chunk.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/cmd.cpython-38.opt-1.pyc b/dist/lib/__pycache__/cmd.cpython-38.opt-1.pyc deleted file mode 100644 index 067004c..0000000 Binary files a/dist/lib/__pycache__/cmd.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/code.cpython-38.opt-1.pyc b/dist/lib/__pycache__/code.cpython-38.opt-1.pyc deleted file mode 100644 index 4f053cf..0000000 Binary files a/dist/lib/__pycache__/code.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/codecs.cpython-38.opt-1.pyc b/dist/lib/__pycache__/codecs.cpython-38.opt-1.pyc deleted file mode 100644 index e4401a8..0000000 Binary files a/dist/lib/__pycache__/codecs.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/codeop.cpython-38.opt-1.pyc b/dist/lib/__pycache__/codeop.cpython-38.opt-1.pyc deleted file mode 100644 index 2ef3071..0000000 Binary files a/dist/lib/__pycache__/codeop.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/colorsys.cpython-38.opt-1.pyc b/dist/lib/__pycache__/colorsys.cpython-38.opt-1.pyc deleted file mode 100644 index 146d1f8..0000000 Binary files a/dist/lib/__pycache__/colorsys.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/compileall.cpython-38.opt-1.pyc b/dist/lib/__pycache__/compileall.cpython-38.opt-1.pyc deleted file mode 100644 index 2601439..0000000 Binary files a/dist/lib/__pycache__/compileall.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/configparser.cpython-38.opt-1.pyc b/dist/lib/__pycache__/configparser.cpython-38.opt-1.pyc deleted file mode 100644 index 1ef5e71..0000000 Binary files a/dist/lib/__pycache__/configparser.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/contextlib.cpython-38.opt-1.pyc b/dist/lib/__pycache__/contextlib.cpython-38.opt-1.pyc deleted file mode 100644 index 9b1cf52..0000000 Binary files a/dist/lib/__pycache__/contextlib.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/contextvars.cpython-38.opt-1.pyc b/dist/lib/__pycache__/contextvars.cpython-38.opt-1.pyc deleted file mode 100644 index 3926f1f..0000000 Binary files a/dist/lib/__pycache__/contextvars.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/copy.cpython-38.opt-1.pyc b/dist/lib/__pycache__/copy.cpython-38.opt-1.pyc deleted file mode 100644 index 510c7aa..0000000 Binary files a/dist/lib/__pycache__/copy.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/copyreg.cpython-38.opt-1.pyc b/dist/lib/__pycache__/copyreg.cpython-38.opt-1.pyc deleted file mode 100644 index ab6d124..0000000 Binary files a/dist/lib/__pycache__/copyreg.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/crypt.cpython-38.opt-1.pyc b/dist/lib/__pycache__/crypt.cpython-38.opt-1.pyc deleted file mode 100644 index 18f51be..0000000 Binary files a/dist/lib/__pycache__/crypt.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/csv.cpython-38.opt-1.pyc b/dist/lib/__pycache__/csv.cpython-38.opt-1.pyc deleted file mode 100644 index 37e36cd..0000000 Binary files a/dist/lib/__pycache__/csv.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/dataclasses.cpython-38.opt-1.pyc b/dist/lib/__pycache__/dataclasses.cpython-38.opt-1.pyc deleted file mode 100644 index 9842296..0000000 Binary files a/dist/lib/__pycache__/dataclasses.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/datetime.cpython-38.opt-1.pyc b/dist/lib/__pycache__/datetime.cpython-38.opt-1.pyc deleted file mode 100644 index 6f0846e..0000000 Binary files a/dist/lib/__pycache__/datetime.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/decimal.cpython-38.opt-1.pyc b/dist/lib/__pycache__/decimal.cpython-38.opt-1.pyc deleted file mode 100644 index e617e66..0000000 Binary files a/dist/lib/__pycache__/decimal.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/difflib.cpython-38.opt-1.pyc b/dist/lib/__pycache__/difflib.cpython-38.opt-1.pyc deleted file mode 100644 index f96648a..0000000 Binary files a/dist/lib/__pycache__/difflib.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/dis.cpython-38.opt-1.pyc b/dist/lib/__pycache__/dis.cpython-38.opt-1.pyc deleted file mode 100644 index 9aed396..0000000 Binary files a/dist/lib/__pycache__/dis.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/doctest.cpython-38.opt-1.pyc b/dist/lib/__pycache__/doctest.cpython-38.opt-1.pyc deleted file mode 100644 index 3bb9a30..0000000 Binary files a/dist/lib/__pycache__/doctest.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/dummy_threading.cpython-38.opt-1.pyc b/dist/lib/__pycache__/dummy_threading.cpython-38.opt-1.pyc deleted file mode 100644 index b41eff8..0000000 Binary files a/dist/lib/__pycache__/dummy_threading.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/enum.cpython-38.opt-1.pyc b/dist/lib/__pycache__/enum.cpython-38.opt-1.pyc deleted file mode 100644 index 83a6d73..0000000 Binary files a/dist/lib/__pycache__/enum.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/filecmp.cpython-38.opt-1.pyc b/dist/lib/__pycache__/filecmp.cpython-38.opt-1.pyc deleted file mode 100644 index 369cec1..0000000 Binary files a/dist/lib/__pycache__/filecmp.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/fileinput.cpython-38.opt-1.pyc b/dist/lib/__pycache__/fileinput.cpython-38.opt-1.pyc deleted file mode 100644 index 4013d1f..0000000 Binary files a/dist/lib/__pycache__/fileinput.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/fnmatch.cpython-38.opt-1.pyc b/dist/lib/__pycache__/fnmatch.cpython-38.opt-1.pyc deleted file mode 100644 index 09af3df..0000000 Binary files a/dist/lib/__pycache__/fnmatch.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/formatter.cpython-38.opt-1.pyc b/dist/lib/__pycache__/formatter.cpython-38.opt-1.pyc deleted file mode 100644 index e4ef0b9..0000000 Binary files a/dist/lib/__pycache__/formatter.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/fractions.cpython-38.opt-1.pyc b/dist/lib/__pycache__/fractions.cpython-38.opt-1.pyc deleted file mode 100644 index 1587bdb..0000000 Binary files a/dist/lib/__pycache__/fractions.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/ftplib.cpython-38.opt-1.pyc b/dist/lib/__pycache__/ftplib.cpython-38.opt-1.pyc deleted file mode 100644 index 055c99b..0000000 Binary files a/dist/lib/__pycache__/ftplib.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/functools.cpython-38.opt-1.pyc b/dist/lib/__pycache__/functools.cpython-38.opt-1.pyc deleted file mode 100644 index 2364dd1..0000000 Binary files a/dist/lib/__pycache__/functools.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/genericpath.cpython-38.opt-1.pyc b/dist/lib/__pycache__/genericpath.cpython-38.opt-1.pyc deleted file mode 100644 index 04870e5..0000000 Binary files a/dist/lib/__pycache__/genericpath.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/getopt.cpython-38.opt-1.pyc b/dist/lib/__pycache__/getopt.cpython-38.opt-1.pyc deleted file mode 100644 index d715306..0000000 Binary files a/dist/lib/__pycache__/getopt.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/getpass.cpython-38.opt-1.pyc b/dist/lib/__pycache__/getpass.cpython-38.opt-1.pyc deleted file mode 100644 index 97185cb..0000000 Binary files a/dist/lib/__pycache__/getpass.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/gettext.cpython-38.opt-1.pyc b/dist/lib/__pycache__/gettext.cpython-38.opt-1.pyc deleted file mode 100644 index 96866a9..0000000 Binary files a/dist/lib/__pycache__/gettext.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/glob.cpython-38.opt-1.pyc b/dist/lib/__pycache__/glob.cpython-38.opt-1.pyc deleted file mode 100644 index ec9025f..0000000 Binary files a/dist/lib/__pycache__/glob.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/gzip.cpython-38.opt-1.pyc b/dist/lib/__pycache__/gzip.cpython-38.opt-1.pyc deleted file mode 100644 index d3e5ac3..0000000 Binary files a/dist/lib/__pycache__/gzip.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/hashlib.cpython-38.opt-1.pyc b/dist/lib/__pycache__/hashlib.cpython-38.opt-1.pyc deleted file mode 100644 index 36df546..0000000 Binary files a/dist/lib/__pycache__/hashlib.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/heapq.cpython-38.opt-1.pyc b/dist/lib/__pycache__/heapq.cpython-38.opt-1.pyc deleted file mode 100644 index 87aea63..0000000 Binary files a/dist/lib/__pycache__/heapq.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/hmac.cpython-38.opt-1.pyc b/dist/lib/__pycache__/hmac.cpython-38.opt-1.pyc deleted file mode 100644 index f51ccf7..0000000 Binary files a/dist/lib/__pycache__/hmac.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/imghdr.cpython-38.opt-1.pyc b/dist/lib/__pycache__/imghdr.cpython-38.opt-1.pyc deleted file mode 100644 index 3aabd20..0000000 Binary files a/dist/lib/__pycache__/imghdr.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/imp.cpython-38.opt-1.pyc b/dist/lib/__pycache__/imp.cpython-38.opt-1.pyc deleted file mode 100644 index 09b41d9..0000000 Binary files a/dist/lib/__pycache__/imp.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/inspect.cpython-38.opt-1.pyc b/dist/lib/__pycache__/inspect.cpython-38.opt-1.pyc deleted file mode 100644 index 69599de..0000000 Binary files a/dist/lib/__pycache__/inspect.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/io.cpython-38.opt-1.pyc b/dist/lib/__pycache__/io.cpython-38.opt-1.pyc deleted file mode 100644 index d536517..0000000 Binary files a/dist/lib/__pycache__/io.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/ipaddress.cpython-38.opt-1.pyc b/dist/lib/__pycache__/ipaddress.cpython-38.opt-1.pyc deleted file mode 100644 index 525ed51..0000000 Binary files a/dist/lib/__pycache__/ipaddress.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/keyword.cpython-38.opt-1.pyc b/dist/lib/__pycache__/keyword.cpython-38.opt-1.pyc deleted file mode 100644 index 21f7ca0..0000000 Binary files a/dist/lib/__pycache__/keyword.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/linecache.cpython-38.opt-1.pyc b/dist/lib/__pycache__/linecache.cpython-38.opt-1.pyc deleted file mode 100644 index 632ad6c..0000000 Binary files a/dist/lib/__pycache__/linecache.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/locale.cpython-38.opt-1.pyc b/dist/lib/__pycache__/locale.cpython-38.opt-1.pyc deleted file mode 100644 index e14f8e9..0000000 Binary files a/dist/lib/__pycache__/locale.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/lzma.cpython-38.opt-1.pyc b/dist/lib/__pycache__/lzma.cpython-38.opt-1.pyc deleted file mode 100644 index 0fed456..0000000 Binary files a/dist/lib/__pycache__/lzma.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/mailbox.cpython-38.opt-1.pyc b/dist/lib/__pycache__/mailbox.cpython-38.opt-1.pyc deleted file mode 100644 index 909488f..0000000 Binary files a/dist/lib/__pycache__/mailbox.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/mailcap.cpython-38.opt-1.pyc b/dist/lib/__pycache__/mailcap.cpython-38.opt-1.pyc deleted file mode 100644 index bd902ea..0000000 Binary files a/dist/lib/__pycache__/mailcap.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/mimetypes.cpython-38.opt-1.pyc b/dist/lib/__pycache__/mimetypes.cpython-38.opt-1.pyc deleted file mode 100644 index 0d574a7..0000000 Binary files a/dist/lib/__pycache__/mimetypes.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/modulefinder.cpython-38.opt-1.pyc b/dist/lib/__pycache__/modulefinder.cpython-38.opt-1.pyc deleted file mode 100644 index 8490816..0000000 Binary files a/dist/lib/__pycache__/modulefinder.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/netrc.cpython-38.opt-1.pyc b/dist/lib/__pycache__/netrc.cpython-38.opt-1.pyc deleted file mode 100644 index a8c6285..0000000 Binary files a/dist/lib/__pycache__/netrc.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/nntplib.cpython-38.opt-1.pyc b/dist/lib/__pycache__/nntplib.cpython-38.opt-1.pyc deleted file mode 100644 index 78eb01b..0000000 Binary files a/dist/lib/__pycache__/nntplib.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/ntpath.cpython-38.opt-1.pyc b/dist/lib/__pycache__/ntpath.cpython-38.opt-1.pyc deleted file mode 100644 index e3907b4..0000000 Binary files a/dist/lib/__pycache__/ntpath.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/nturl2path.cpython-38.opt-1.pyc b/dist/lib/__pycache__/nturl2path.cpython-38.opt-1.pyc deleted file mode 100644 index 6ce2d3d..0000000 Binary files a/dist/lib/__pycache__/nturl2path.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/numbers.cpython-38.opt-1.pyc b/dist/lib/__pycache__/numbers.cpython-38.opt-1.pyc deleted file mode 100644 index 8136e54..0000000 Binary files a/dist/lib/__pycache__/numbers.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/opcode.cpython-38.opt-1.pyc b/dist/lib/__pycache__/opcode.cpython-38.opt-1.pyc deleted file mode 100644 index 2779e5d..0000000 Binary files a/dist/lib/__pycache__/opcode.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/operator.cpython-38.opt-1.pyc b/dist/lib/__pycache__/operator.cpython-38.opt-1.pyc deleted file mode 100644 index ba6abd9..0000000 Binary files a/dist/lib/__pycache__/operator.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/optparse.cpython-38.opt-1.pyc b/dist/lib/__pycache__/optparse.cpython-38.opt-1.pyc deleted file mode 100644 index 1d0e458..0000000 Binary files a/dist/lib/__pycache__/optparse.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/os.cpython-38.opt-1.pyc b/dist/lib/__pycache__/os.cpython-38.opt-1.pyc deleted file mode 100644 index 89a0695..0000000 Binary files a/dist/lib/__pycache__/os.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/pathlib.cpython-38.opt-1.pyc b/dist/lib/__pycache__/pathlib.cpython-38.opt-1.pyc deleted file mode 100644 index a0b78f0..0000000 Binary files a/dist/lib/__pycache__/pathlib.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/pdb.cpython-38.opt-1.pyc b/dist/lib/__pycache__/pdb.cpython-38.opt-1.pyc deleted file mode 100644 index 1f91515..0000000 Binary files a/dist/lib/__pycache__/pdb.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/pickle.cpython-38.opt-1.pyc b/dist/lib/__pycache__/pickle.cpython-38.opt-1.pyc deleted file mode 100644 index a4f11d2..0000000 Binary files a/dist/lib/__pycache__/pickle.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/pickletools.cpython-38.opt-1.pyc b/dist/lib/__pycache__/pickletools.cpython-38.opt-1.pyc deleted file mode 100644 index fccdbf5..0000000 Binary files a/dist/lib/__pycache__/pickletools.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/pipes.cpython-38.opt-1.pyc b/dist/lib/__pycache__/pipes.cpython-38.opt-1.pyc deleted file mode 100644 index 80a45f5..0000000 Binary files a/dist/lib/__pycache__/pipes.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/pkgutil.cpython-38.opt-1.pyc b/dist/lib/__pycache__/pkgutil.cpython-38.opt-1.pyc deleted file mode 100644 index 29128d9..0000000 Binary files a/dist/lib/__pycache__/pkgutil.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/platform.cpython-38.opt-1.pyc b/dist/lib/__pycache__/platform.cpython-38.opt-1.pyc deleted file mode 100644 index a2045c9..0000000 Binary files a/dist/lib/__pycache__/platform.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/plistlib.cpython-38.opt-1.pyc b/dist/lib/__pycache__/plistlib.cpython-38.opt-1.pyc deleted file mode 100644 index 4f03d37..0000000 Binary files a/dist/lib/__pycache__/plistlib.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/poplib.cpython-38.opt-1.pyc b/dist/lib/__pycache__/poplib.cpython-38.opt-1.pyc deleted file mode 100644 index 6cc0fed..0000000 Binary files a/dist/lib/__pycache__/poplib.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/posixpath.cpython-38.opt-1.pyc b/dist/lib/__pycache__/posixpath.cpython-38.opt-1.pyc deleted file mode 100644 index 6d89aaa..0000000 Binary files a/dist/lib/__pycache__/posixpath.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/pprint.cpython-38.opt-1.pyc b/dist/lib/__pycache__/pprint.cpython-38.opt-1.pyc deleted file mode 100644 index 6b187ac..0000000 Binary files a/dist/lib/__pycache__/pprint.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/profile.cpython-38.opt-1.pyc b/dist/lib/__pycache__/profile.cpython-38.opt-1.pyc deleted file mode 100644 index d223719..0000000 Binary files a/dist/lib/__pycache__/profile.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/pstats.cpython-38.opt-1.pyc b/dist/lib/__pycache__/pstats.cpython-38.opt-1.pyc deleted file mode 100644 index 47bb39d..0000000 Binary files a/dist/lib/__pycache__/pstats.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/pty.cpython-38.opt-1.pyc b/dist/lib/__pycache__/pty.cpython-38.opt-1.pyc deleted file mode 100644 index b0d8841..0000000 Binary files a/dist/lib/__pycache__/pty.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/py_compile.cpython-38.opt-1.pyc b/dist/lib/__pycache__/py_compile.cpython-38.opt-1.pyc deleted file mode 100644 index 9c948e2..0000000 Binary files a/dist/lib/__pycache__/py_compile.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/pyclbr.cpython-38.opt-1.pyc b/dist/lib/__pycache__/pyclbr.cpython-38.opt-1.pyc deleted file mode 100644 index 21b02d8..0000000 Binary files a/dist/lib/__pycache__/pyclbr.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/pydoc.cpython-38.opt-1.pyc b/dist/lib/__pycache__/pydoc.cpython-38.opt-1.pyc deleted file mode 100644 index 36ec757..0000000 Binary files a/dist/lib/__pycache__/pydoc.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/queue.cpython-38.opt-1.pyc b/dist/lib/__pycache__/queue.cpython-38.opt-1.pyc deleted file mode 100644 index 366e9a9..0000000 Binary files a/dist/lib/__pycache__/queue.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/quopri.cpython-38.opt-1.pyc b/dist/lib/__pycache__/quopri.cpython-38.opt-1.pyc deleted file mode 100644 index 19c8830..0000000 Binary files a/dist/lib/__pycache__/quopri.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/random.cpython-38.opt-1.pyc b/dist/lib/__pycache__/random.cpython-38.opt-1.pyc deleted file mode 100644 index 929dcd5..0000000 Binary files a/dist/lib/__pycache__/random.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/re.cpython-38.opt-1.pyc b/dist/lib/__pycache__/re.cpython-38.opt-1.pyc deleted file mode 100644 index 664ee7f..0000000 Binary files a/dist/lib/__pycache__/re.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/reprlib.cpython-38.opt-1.pyc b/dist/lib/__pycache__/reprlib.cpython-38.opt-1.pyc deleted file mode 100644 index 993b8c8..0000000 Binary files a/dist/lib/__pycache__/reprlib.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/rlcompleter.cpython-38.opt-1.pyc b/dist/lib/__pycache__/rlcompleter.cpython-38.opt-1.pyc deleted file mode 100644 index b7dbaa9..0000000 Binary files a/dist/lib/__pycache__/rlcompleter.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/runpy.cpython-38.opt-1.pyc b/dist/lib/__pycache__/runpy.cpython-38.opt-1.pyc deleted file mode 100644 index 5fbcd6c..0000000 Binary files a/dist/lib/__pycache__/runpy.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/sched.cpython-38.opt-1.pyc b/dist/lib/__pycache__/sched.cpython-38.opt-1.pyc deleted file mode 100644 index 84b675e..0000000 Binary files a/dist/lib/__pycache__/sched.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/secrets.cpython-38.opt-1.pyc b/dist/lib/__pycache__/secrets.cpython-38.opt-1.pyc deleted file mode 100644 index de7e332..0000000 Binary files a/dist/lib/__pycache__/secrets.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/selectors.cpython-38.opt-1.pyc b/dist/lib/__pycache__/selectors.cpython-38.opt-1.pyc deleted file mode 100644 index 132374d..0000000 Binary files a/dist/lib/__pycache__/selectors.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/shelve.cpython-38.opt-1.pyc b/dist/lib/__pycache__/shelve.cpython-38.opt-1.pyc deleted file mode 100644 index eadd59c..0000000 Binary files a/dist/lib/__pycache__/shelve.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/shlex.cpython-38.opt-1.pyc b/dist/lib/__pycache__/shlex.cpython-38.opt-1.pyc deleted file mode 100644 index 844c728..0000000 Binary files a/dist/lib/__pycache__/shlex.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/shutil.cpython-38.opt-1.pyc b/dist/lib/__pycache__/shutil.cpython-38.opt-1.pyc deleted file mode 100644 index 3d45ffc..0000000 Binary files a/dist/lib/__pycache__/shutil.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/signal.cpython-38.opt-1.pyc b/dist/lib/__pycache__/signal.cpython-38.opt-1.pyc deleted file mode 100644 index 3b89ad7..0000000 Binary files a/dist/lib/__pycache__/signal.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/site.cpython-38.opt-1.pyc b/dist/lib/__pycache__/site.cpython-38.opt-1.pyc deleted file mode 100644 index 17f6e0d..0000000 Binary files a/dist/lib/__pycache__/site.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/smtpd.cpython-38.opt-1.pyc b/dist/lib/__pycache__/smtpd.cpython-38.opt-1.pyc deleted file mode 100644 index e015a68..0000000 Binary files a/dist/lib/__pycache__/smtpd.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/smtplib.cpython-38.opt-1.pyc b/dist/lib/__pycache__/smtplib.cpython-38.opt-1.pyc deleted file mode 100644 index d65869a..0000000 Binary files a/dist/lib/__pycache__/smtplib.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/sndhdr.cpython-38.opt-1.pyc b/dist/lib/__pycache__/sndhdr.cpython-38.opt-1.pyc deleted file mode 100644 index 6401418..0000000 Binary files a/dist/lib/__pycache__/sndhdr.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/socket.cpython-38.opt-1.pyc b/dist/lib/__pycache__/socket.cpython-38.opt-1.pyc deleted file mode 100644 index 9327ad9..0000000 Binary files a/dist/lib/__pycache__/socket.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/socketserver.cpython-38.opt-1.pyc b/dist/lib/__pycache__/socketserver.cpython-38.opt-1.pyc deleted file mode 100644 index da0b284..0000000 Binary files a/dist/lib/__pycache__/socketserver.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/sre_compile.cpython-38.opt-1.pyc b/dist/lib/__pycache__/sre_compile.cpython-38.opt-1.pyc deleted file mode 100644 index 0a3d186..0000000 Binary files a/dist/lib/__pycache__/sre_compile.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/sre_constants.cpython-38.opt-1.pyc b/dist/lib/__pycache__/sre_constants.cpython-38.opt-1.pyc deleted file mode 100644 index 51a4a38..0000000 Binary files a/dist/lib/__pycache__/sre_constants.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/sre_parse.cpython-38.opt-1.pyc b/dist/lib/__pycache__/sre_parse.cpython-38.opt-1.pyc deleted file mode 100644 index 40cb7a1..0000000 Binary files a/dist/lib/__pycache__/sre_parse.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/ssl.cpython-38.opt-1.pyc b/dist/lib/__pycache__/ssl.cpython-38.opt-1.pyc deleted file mode 100644 index 816041f..0000000 Binary files a/dist/lib/__pycache__/ssl.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/stat.cpython-38.opt-1.pyc b/dist/lib/__pycache__/stat.cpython-38.opt-1.pyc deleted file mode 100644 index 65897aa..0000000 Binary files a/dist/lib/__pycache__/stat.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/statistics.cpython-38.opt-1.pyc b/dist/lib/__pycache__/statistics.cpython-38.opt-1.pyc deleted file mode 100644 index 0c2a73d..0000000 Binary files a/dist/lib/__pycache__/statistics.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/string.cpython-38.opt-1.pyc b/dist/lib/__pycache__/string.cpython-38.opt-1.pyc deleted file mode 100644 index d899244..0000000 Binary files a/dist/lib/__pycache__/string.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/stringprep.cpython-38.opt-1.pyc b/dist/lib/__pycache__/stringprep.cpython-38.opt-1.pyc deleted file mode 100644 index 9e63e64..0000000 Binary files a/dist/lib/__pycache__/stringprep.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/struct.cpython-38.opt-1.pyc b/dist/lib/__pycache__/struct.cpython-38.opt-1.pyc deleted file mode 100644 index 6dfcd7f..0000000 Binary files a/dist/lib/__pycache__/struct.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/subprocess.cpython-38.opt-1.pyc b/dist/lib/__pycache__/subprocess.cpython-38.opt-1.pyc deleted file mode 100644 index c0de491..0000000 Binary files a/dist/lib/__pycache__/subprocess.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/sunau.cpython-38.opt-1.pyc b/dist/lib/__pycache__/sunau.cpython-38.opt-1.pyc deleted file mode 100644 index 0fcd2fa..0000000 Binary files a/dist/lib/__pycache__/sunau.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/symbol.cpython-38.opt-1.pyc b/dist/lib/__pycache__/symbol.cpython-38.opt-1.pyc deleted file mode 100644 index 2f28d22..0000000 Binary files a/dist/lib/__pycache__/symbol.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/symtable.cpython-38.opt-1.pyc b/dist/lib/__pycache__/symtable.cpython-38.opt-1.pyc deleted file mode 100644 index 8765d5b..0000000 Binary files a/dist/lib/__pycache__/symtable.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/sysconfig.cpython-38.opt-1.pyc b/dist/lib/__pycache__/sysconfig.cpython-38.opt-1.pyc deleted file mode 100644 index 3a81836..0000000 Binary files a/dist/lib/__pycache__/sysconfig.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/tabnanny.cpython-38.opt-1.pyc b/dist/lib/__pycache__/tabnanny.cpython-38.opt-1.pyc deleted file mode 100644 index d8d8daa..0000000 Binary files a/dist/lib/__pycache__/tabnanny.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/tarfile.cpython-38.opt-1.pyc b/dist/lib/__pycache__/tarfile.cpython-38.opt-1.pyc deleted file mode 100644 index 7e70b5c..0000000 Binary files a/dist/lib/__pycache__/tarfile.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/telnetlib.cpython-38.opt-1.pyc b/dist/lib/__pycache__/telnetlib.cpython-38.opt-1.pyc deleted file mode 100644 index e4a82aa..0000000 Binary files a/dist/lib/__pycache__/telnetlib.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/tempfile.cpython-38.opt-1.pyc b/dist/lib/__pycache__/tempfile.cpython-38.opt-1.pyc deleted file mode 100644 index 29cae83..0000000 Binary files a/dist/lib/__pycache__/tempfile.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/textwrap.cpython-38.opt-1.pyc b/dist/lib/__pycache__/textwrap.cpython-38.opt-1.pyc deleted file mode 100644 index 6761ea2..0000000 Binary files a/dist/lib/__pycache__/textwrap.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/this.cpython-38.opt-1.pyc b/dist/lib/__pycache__/this.cpython-38.opt-1.pyc deleted file mode 100644 index c5e4f71..0000000 Binary files a/dist/lib/__pycache__/this.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/threading.cpython-38.opt-1.pyc b/dist/lib/__pycache__/threading.cpython-38.opt-1.pyc deleted file mode 100644 index fbd4e99..0000000 Binary files a/dist/lib/__pycache__/threading.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/timeit.cpython-38.opt-1.pyc b/dist/lib/__pycache__/timeit.cpython-38.opt-1.pyc deleted file mode 100644 index aeef11a..0000000 Binary files a/dist/lib/__pycache__/timeit.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/token.cpython-38.opt-1.pyc b/dist/lib/__pycache__/token.cpython-38.opt-1.pyc deleted file mode 100644 index 20b7d78..0000000 Binary files a/dist/lib/__pycache__/token.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/tokenize.cpython-38.opt-1.pyc b/dist/lib/__pycache__/tokenize.cpython-38.opt-1.pyc deleted file mode 100644 index edb2158..0000000 Binary files a/dist/lib/__pycache__/tokenize.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/trace.cpython-38.opt-1.pyc b/dist/lib/__pycache__/trace.cpython-38.opt-1.pyc deleted file mode 100644 index f23f1db..0000000 Binary files a/dist/lib/__pycache__/trace.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/traceback.cpython-38.opt-1.pyc b/dist/lib/__pycache__/traceback.cpython-38.opt-1.pyc deleted file mode 100644 index 00d5641..0000000 Binary files a/dist/lib/__pycache__/traceback.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/tracemalloc.cpython-38.opt-1.pyc b/dist/lib/__pycache__/tracemalloc.cpython-38.opt-1.pyc deleted file mode 100644 index da863e8..0000000 Binary files a/dist/lib/__pycache__/tracemalloc.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/tty.cpython-38.opt-1.pyc b/dist/lib/__pycache__/tty.cpython-38.opt-1.pyc deleted file mode 100644 index 494de8b..0000000 Binary files a/dist/lib/__pycache__/tty.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/types.cpython-38.opt-1.pyc b/dist/lib/__pycache__/types.cpython-38.opt-1.pyc deleted file mode 100644 index f970c7a..0000000 Binary files a/dist/lib/__pycache__/types.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/typing.cpython-38.opt-1.pyc b/dist/lib/__pycache__/typing.cpython-38.opt-1.pyc deleted file mode 100644 index 34decb7..0000000 Binary files a/dist/lib/__pycache__/typing.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/uu.cpython-38.opt-1.pyc b/dist/lib/__pycache__/uu.cpython-38.opt-1.pyc deleted file mode 100644 index 2ee7ac0..0000000 Binary files a/dist/lib/__pycache__/uu.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/uuid.cpython-38.opt-1.pyc b/dist/lib/__pycache__/uuid.cpython-38.opt-1.pyc deleted file mode 100644 index 6f792d6..0000000 Binary files a/dist/lib/__pycache__/uuid.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/warnings.cpython-38.opt-1.pyc b/dist/lib/__pycache__/warnings.cpython-38.opt-1.pyc deleted file mode 100644 index ecadb43..0000000 Binary files a/dist/lib/__pycache__/warnings.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/wave.cpython-38.opt-1.pyc b/dist/lib/__pycache__/wave.cpython-38.opt-1.pyc deleted file mode 100644 index 7976573..0000000 Binary files a/dist/lib/__pycache__/wave.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/weakref.cpython-38.opt-1.pyc b/dist/lib/__pycache__/weakref.cpython-38.opt-1.pyc deleted file mode 100644 index 645c304..0000000 Binary files a/dist/lib/__pycache__/weakref.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/webbrowser.cpython-38.opt-1.pyc b/dist/lib/__pycache__/webbrowser.cpython-38.opt-1.pyc deleted file mode 100644 index 5ba3b3b..0000000 Binary files a/dist/lib/__pycache__/webbrowser.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/xdrlib.cpython-38.opt-1.pyc b/dist/lib/__pycache__/xdrlib.cpython-38.opt-1.pyc deleted file mode 100644 index 7a6e822..0000000 Binary files a/dist/lib/__pycache__/xdrlib.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/zipapp.cpython-38.opt-1.pyc b/dist/lib/__pycache__/zipapp.cpython-38.opt-1.pyc deleted file mode 100644 index 409eb33..0000000 Binary files a/dist/lib/__pycache__/zipapp.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/zipfile.cpython-38.opt-1.pyc b/dist/lib/__pycache__/zipfile.cpython-38.opt-1.pyc deleted file mode 100644 index ace204f..0000000 Binary files a/dist/lib/__pycache__/zipfile.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/__pycache__/zipimport.cpython-38.opt-1.pyc b/dist/lib/__pycache__/zipimport.cpython-38.opt-1.pyc deleted file mode 100644 index 12d9fd3..0000000 Binary files a/dist/lib/__pycache__/zipimport.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/_bootlocale.py b/dist/lib/_bootlocale.py deleted file mode 100644 index 3273a3b..0000000 --- a/dist/lib/_bootlocale.py +++ /dev/null @@ -1,46 +0,0 @@ -"""A minimal subset of the locale module used at interpreter startup -(imported by the _io module), in order to reduce startup time. - -Don't import directly from third-party code; use the `locale` module instead! -""" - -import sys -import _locale - -if sys.platform.startswith("win"): - def getpreferredencoding(do_setlocale=True): - if sys.flags.utf8_mode: - return 'UTF-8' - return _locale._getdefaultlocale()[1] -else: - try: - _locale.CODESET - except AttributeError: - if hasattr(sys, 'getandroidapilevel'): - # On Android langinfo.h and CODESET are missing, and UTF-8 is - # always used in mbstowcs() and wcstombs(). - def getpreferredencoding(do_setlocale=True): - return 'UTF-8' - else: - def getpreferredencoding(do_setlocale=True): - if sys.flags.utf8_mode: - return 'UTF-8' - # This path for legacy systems needs the more complex - # getdefaultlocale() function, import the full locale module. - import locale - return locale.getpreferredencoding(do_setlocale) - else: - def getpreferredencoding(do_setlocale=True): - assert not do_setlocale - if sys.flags.utf8_mode: - return 'UTF-8' - result = _locale.nl_langinfo(_locale.CODESET) - if not result and sys.platform == 'darwin': - # nl_langinfo can return an empty string - # when the setting has an invalid value. - # Default to UTF-8 in that case because - # UTF-8 is the default charset on OSX and - # returning nothing will crash the - # interpreter. - result = 'UTF-8' - return result diff --git a/dist/lib/_collections_abc.py b/dist/lib/_collections_abc.py deleted file mode 100644 index 2b2ddba..0000000 --- a/dist/lib/_collections_abc.py +++ /dev/null @@ -1,1004 +0,0 @@ -# Copyright 2007 Google, Inc. All Rights Reserved. -# Licensed to PSF under a Contributor Agreement. - -"""Abstract Base Classes (ABCs) for collections, according to PEP 3119. - -Unit tests are in test_collections. -""" - -from abc import ABCMeta, abstractmethod -import sys - -__all__ = ["Awaitable", "Coroutine", - "AsyncIterable", "AsyncIterator", "AsyncGenerator", - "Hashable", "Iterable", "Iterator", "Generator", "Reversible", - "Sized", "Container", "Callable", "Collection", - "Set", "MutableSet", - "Mapping", "MutableMapping", - "MappingView", "KeysView", "ItemsView", "ValuesView", - "Sequence", "MutableSequence", - "ByteString", - ] - -# This module has been renamed from collections.abc to _collections_abc to -# speed up interpreter startup. Some of the types such as MutableMapping are -# required early but collections module imports a lot of other modules. -# See issue #19218 -__name__ = "collections.abc" - -# Private list of types that we want to register with the various ABCs -# so that they will pass tests like: -# it = iter(somebytearray) -# assert isinstance(it, Iterable) -# Note: in other implementations, these types might not be distinct -# and they may have their own implementation specific types that -# are not included on this list. -bytes_iterator = type(iter(b'')) -bytearray_iterator = type(iter(bytearray())) -#callable_iterator = ??? -dict_keyiterator = type(iter({}.keys())) -dict_valueiterator = type(iter({}.values())) -dict_itemiterator = type(iter({}.items())) -list_iterator = type(iter([])) -list_reverseiterator = type(iter(reversed([]))) -range_iterator = type(iter(range(0))) -longrange_iterator = type(iter(range(1 << 1000))) -set_iterator = type(iter(set())) -str_iterator = type(iter("")) -tuple_iterator = type(iter(())) -zip_iterator = type(iter(zip())) -## views ## -dict_keys = type({}.keys()) -dict_values = type({}.values()) -dict_items = type({}.items()) -## misc ## -mappingproxy = type(type.__dict__) -generator = type((lambda: (yield))()) -## coroutine ## -async def _coro(): pass -_coro = _coro() -coroutine = type(_coro) -_coro.close() # Prevent ResourceWarning -del _coro -## asynchronous generator ## -async def _ag(): yield -_ag = _ag() -async_generator = type(_ag) -del _ag - - -### ONE-TRICK PONIES ### - -def _check_methods(C, *methods): - mro = C.__mro__ - for method in methods: - for B in mro: - if method in B.__dict__: - if B.__dict__[method] is None: - return NotImplemented - break - else: - return NotImplemented - return True - -class Hashable(metaclass=ABCMeta): - - __slots__ = () - - @abstractmethod - def __hash__(self): - return 0 - - @classmethod - def __subclasshook__(cls, C): - if cls is Hashable: - return _check_methods(C, "__hash__") - return NotImplemented - - -class Awaitable(metaclass=ABCMeta): - - __slots__ = () - - @abstractmethod - def __await__(self): - yield - - @classmethod - def __subclasshook__(cls, C): - if cls is Awaitable: - return _check_methods(C, "__await__") - return NotImplemented - - -class Coroutine(Awaitable): - - __slots__ = () - - @abstractmethod - def send(self, value): - """Send a value into the coroutine. - Return next yielded value or raise StopIteration. - """ - raise StopIteration - - @abstractmethod - def throw(self, typ, val=None, tb=None): - """Raise an exception in the coroutine. - Return next yielded value or raise StopIteration. - """ - if val is None: - if tb is None: - raise typ - val = typ() - if tb is not None: - val = val.with_traceback(tb) - raise val - - def close(self): - """Raise GeneratorExit inside coroutine. - """ - try: - self.throw(GeneratorExit) - except (GeneratorExit, StopIteration): - pass - else: - raise RuntimeError("coroutine ignored GeneratorExit") - - @classmethod - def __subclasshook__(cls, C): - if cls is Coroutine: - return _check_methods(C, '__await__', 'send', 'throw', 'close') - return NotImplemented - - -Coroutine.register(coroutine) - - -class AsyncIterable(metaclass=ABCMeta): - - __slots__ = () - - @abstractmethod - def __aiter__(self): - return AsyncIterator() - - @classmethod - def __subclasshook__(cls, C): - if cls is AsyncIterable: - return _check_methods(C, "__aiter__") - return NotImplemented - - -class AsyncIterator(AsyncIterable): - - __slots__ = () - - @abstractmethod - async def __anext__(self): - """Return the next item or raise StopAsyncIteration when exhausted.""" - raise StopAsyncIteration - - def __aiter__(self): - return self - - @classmethod - def __subclasshook__(cls, C): - if cls is AsyncIterator: - return _check_methods(C, "__anext__", "__aiter__") - return NotImplemented - - -class AsyncGenerator(AsyncIterator): - - __slots__ = () - - async def __anext__(self): - """Return the next item from the asynchronous generator. - When exhausted, raise StopAsyncIteration. - """ - return await self.asend(None) - - @abstractmethod - async def asend(self, value): - """Send a value into the asynchronous generator. - Return next yielded value or raise StopAsyncIteration. - """ - raise StopAsyncIteration - - @abstractmethod - async def athrow(self, typ, val=None, tb=None): - """Raise an exception in the asynchronous generator. - Return next yielded value or raise StopAsyncIteration. - """ - if val is None: - if tb is None: - raise typ - val = typ() - if tb is not None: - val = val.with_traceback(tb) - raise val - - async def aclose(self): - """Raise GeneratorExit inside coroutine. - """ - try: - await self.athrow(GeneratorExit) - except (GeneratorExit, StopAsyncIteration): - pass - else: - raise RuntimeError("asynchronous generator ignored GeneratorExit") - - @classmethod - def __subclasshook__(cls, C): - if cls is AsyncGenerator: - return _check_methods(C, '__aiter__', '__anext__', - 'asend', 'athrow', 'aclose') - return NotImplemented - - -AsyncGenerator.register(async_generator) - - -class Iterable(metaclass=ABCMeta): - - __slots__ = () - - @abstractmethod - def __iter__(self): - while False: - yield None - - @classmethod - def __subclasshook__(cls, C): - if cls is Iterable: - return _check_methods(C, "__iter__") - return NotImplemented - - -class Iterator(Iterable): - - __slots__ = () - - @abstractmethod - def __next__(self): - 'Return the next item from the iterator. When exhausted, raise StopIteration' - raise StopIteration - - def __iter__(self): - return self - - @classmethod - def __subclasshook__(cls, C): - if cls is Iterator: - return _check_methods(C, '__iter__', '__next__') - return NotImplemented - -Iterator.register(bytes_iterator) -Iterator.register(bytearray_iterator) -#Iterator.register(callable_iterator) -Iterator.register(dict_keyiterator) -Iterator.register(dict_valueiterator) -Iterator.register(dict_itemiterator) -Iterator.register(list_iterator) -Iterator.register(list_reverseiterator) -Iterator.register(range_iterator) -Iterator.register(longrange_iterator) -Iterator.register(set_iterator) -Iterator.register(str_iterator) -Iterator.register(tuple_iterator) -Iterator.register(zip_iterator) - - -class Reversible(Iterable): - - __slots__ = () - - @abstractmethod - def __reversed__(self): - while False: - yield None - - @classmethod - def __subclasshook__(cls, C): - if cls is Reversible: - return _check_methods(C, "__reversed__", "__iter__") - return NotImplemented - - -class Generator(Iterator): - - __slots__ = () - - def __next__(self): - """Return the next item from the generator. - When exhausted, raise StopIteration. - """ - return self.send(None) - - @abstractmethod - def send(self, value): - """Send a value into the generator. - Return next yielded value or raise StopIteration. - """ - raise StopIteration - - @abstractmethod - def throw(self, typ, val=None, tb=None): - """Raise an exception in the generator. - Return next yielded value or raise StopIteration. - """ - if val is None: - if tb is None: - raise typ - val = typ() - if tb is not None: - val = val.with_traceback(tb) - raise val - - def close(self): - """Raise GeneratorExit inside generator. - """ - try: - self.throw(GeneratorExit) - except (GeneratorExit, StopIteration): - pass - else: - raise RuntimeError("generator ignored GeneratorExit") - - @classmethod - def __subclasshook__(cls, C): - if cls is Generator: - return _check_methods(C, '__iter__', '__next__', - 'send', 'throw', 'close') - return NotImplemented - -Generator.register(generator) - - -class Sized(metaclass=ABCMeta): - - __slots__ = () - - @abstractmethod - def __len__(self): - return 0 - - @classmethod - def __subclasshook__(cls, C): - if cls is Sized: - return _check_methods(C, "__len__") - return NotImplemented - - -class Container(metaclass=ABCMeta): - - __slots__ = () - - @abstractmethod - def __contains__(self, x): - return False - - @classmethod - def __subclasshook__(cls, C): - if cls is Container: - return _check_methods(C, "__contains__") - return NotImplemented - -class Collection(Sized, Iterable, Container): - - __slots__ = () - - @classmethod - def __subclasshook__(cls, C): - if cls is Collection: - return _check_methods(C, "__len__", "__iter__", "__contains__") - return NotImplemented - -class Callable(metaclass=ABCMeta): - - __slots__ = () - - @abstractmethod - def __call__(self, *args, **kwds): - return False - - @classmethod - def __subclasshook__(cls, C): - if cls is Callable: - return _check_methods(C, "__call__") - return NotImplemented - - -### SETS ### - - -class Set(Collection): - - """A set is a finite, iterable container. - - This class provides concrete generic implementations of all - methods except for __contains__, __iter__ and __len__. - - To override the comparisons (presumably for speed, as the - semantics are fixed), redefine __le__ and __ge__, - then the other operations will automatically follow suit. - """ - - __slots__ = () - - def __le__(self, other): - if not isinstance(other, Set): - return NotImplemented - if len(self) > len(other): - return False - for elem in self: - if elem not in other: - return False - return True - - def __lt__(self, other): - if not isinstance(other, Set): - return NotImplemented - return len(self) < len(other) and self.__le__(other) - - def __gt__(self, other): - if not isinstance(other, Set): - return NotImplemented - return len(self) > len(other) and self.__ge__(other) - - def __ge__(self, other): - if not isinstance(other, Set): - return NotImplemented - if len(self) < len(other): - return False - for elem in other: - if elem not in self: - return False - return True - - def __eq__(self, other): - if not isinstance(other, Set): - return NotImplemented - return len(self) == len(other) and self.__le__(other) - - @classmethod - def _from_iterable(cls, it): - '''Construct an instance of the class from any iterable input. - - Must override this method if the class constructor signature - does not accept an iterable for an input. - ''' - return cls(it) - - def __and__(self, other): - if not isinstance(other, Iterable): - return NotImplemented - return self._from_iterable(value for value in other if value in self) - - __rand__ = __and__ - - def isdisjoint(self, other): - 'Return True if two sets have a null intersection.' - for value in other: - if value in self: - return False - return True - - def __or__(self, other): - if not isinstance(other, Iterable): - return NotImplemented - chain = (e for s in (self, other) for e in s) - return self._from_iterable(chain) - - __ror__ = __or__ - - def __sub__(self, other): - if not isinstance(other, Set): - if not isinstance(other, Iterable): - return NotImplemented - other = self._from_iterable(other) - return self._from_iterable(value for value in self - if value not in other) - - def __rsub__(self, other): - if not isinstance(other, Set): - if not isinstance(other, Iterable): - return NotImplemented - other = self._from_iterable(other) - return self._from_iterable(value for value in other - if value not in self) - - def __xor__(self, other): - if not isinstance(other, Set): - if not isinstance(other, Iterable): - return NotImplemented - other = self._from_iterable(other) - return (self - other) | (other - self) - - __rxor__ = __xor__ - - def _hash(self): - """Compute the hash value of a set. - - Note that we don't define __hash__: not all sets are hashable. - But if you define a hashable set type, its __hash__ should - call this function. - - This must be compatible __eq__. - - All sets ought to compare equal if they contain the same - elements, regardless of how they are implemented, and - regardless of the order of the elements; so there's not much - freedom for __eq__ or __hash__. We match the algorithm used - by the built-in frozenset type. - """ - MAX = sys.maxsize - MASK = 2 * MAX + 1 - n = len(self) - h = 1927868237 * (n + 1) - h &= MASK - for x in self: - hx = hash(x) - h ^= (hx ^ (hx << 16) ^ 89869747) * 3644798167 - h &= MASK - h = h * 69069 + 907133923 - h &= MASK - if h > MAX: - h -= MASK + 1 - if h == -1: - h = 590923713 - return h - -Set.register(frozenset) - - -class MutableSet(Set): - """A mutable set is a finite, iterable container. - - This class provides concrete generic implementations of all - methods except for __contains__, __iter__, __len__, - add(), and discard(). - - To override the comparisons (presumably for speed, as the - semantics are fixed), all you have to do is redefine __le__ and - then the other operations will automatically follow suit. - """ - - __slots__ = () - - @abstractmethod - def add(self, value): - """Add an element.""" - raise NotImplementedError - - @abstractmethod - def discard(self, value): - """Remove an element. Do not raise an exception if absent.""" - raise NotImplementedError - - def remove(self, value): - """Remove an element. If not a member, raise a KeyError.""" - if value not in self: - raise KeyError(value) - self.discard(value) - - def pop(self): - """Return the popped value. Raise KeyError if empty.""" - it = iter(self) - try: - value = next(it) - except StopIteration: - raise KeyError from None - self.discard(value) - return value - - def clear(self): - """This is slow (creates N new iterators!) but effective.""" - try: - while True: - self.pop() - except KeyError: - pass - - def __ior__(self, it): - for value in it: - self.add(value) - return self - - def __iand__(self, it): - for value in (self - it): - self.discard(value) - return self - - def __ixor__(self, it): - if it is self: - self.clear() - else: - if not isinstance(it, Set): - it = self._from_iterable(it) - for value in it: - if value in self: - self.discard(value) - else: - self.add(value) - return self - - def __isub__(self, it): - if it is self: - self.clear() - else: - for value in it: - self.discard(value) - return self - -MutableSet.register(set) - - -### MAPPINGS ### - - -class Mapping(Collection): - - __slots__ = () - - """A Mapping is a generic container for associating key/value - pairs. - - This class provides concrete generic implementations of all - methods except for __getitem__, __iter__, and __len__. - - """ - - @abstractmethod - def __getitem__(self, key): - raise KeyError - - def get(self, key, default=None): - 'D.get(k[,d]) -> D[k] if k in D, else d. d defaults to None.' - try: - return self[key] - except KeyError: - return default - - def __contains__(self, key): - try: - self[key] - except KeyError: - return False - else: - return True - - def keys(self): - "D.keys() -> a set-like object providing a view on D's keys" - return KeysView(self) - - def items(self): - "D.items() -> a set-like object providing a view on D's items" - return ItemsView(self) - - def values(self): - "D.values() -> an object providing a view on D's values" - return ValuesView(self) - - def __eq__(self, other): - if not isinstance(other, Mapping): - return NotImplemented - return dict(self.items()) == dict(other.items()) - - __reversed__ = None - -Mapping.register(mappingproxy) - - -class MappingView(Sized): - - __slots__ = '_mapping', - - def __init__(self, mapping): - self._mapping = mapping - - def __len__(self): - return len(self._mapping) - - def __repr__(self): - return '{0.__class__.__name__}({0._mapping!r})'.format(self) - - -class KeysView(MappingView, Set): - - __slots__ = () - - @classmethod - def _from_iterable(self, it): - return set(it) - - def __contains__(self, key): - return key in self._mapping - - def __iter__(self): - yield from self._mapping - -KeysView.register(dict_keys) - - -class ItemsView(MappingView, Set): - - __slots__ = () - - @classmethod - def _from_iterable(self, it): - return set(it) - - def __contains__(self, item): - key, value = item - try: - v = self._mapping[key] - except KeyError: - return False - else: - return v is value or v == value - - def __iter__(self): - for key in self._mapping: - yield (key, self._mapping[key]) - -ItemsView.register(dict_items) - - -class ValuesView(MappingView, Collection): - - __slots__ = () - - def __contains__(self, value): - for key in self._mapping: - v = self._mapping[key] - if v is value or v == value: - return True - return False - - def __iter__(self): - for key in self._mapping: - yield self._mapping[key] - -ValuesView.register(dict_values) - - -class MutableMapping(Mapping): - - __slots__ = () - - """A MutableMapping is a generic container for associating - key/value pairs. - - This class provides concrete generic implementations of all - methods except for __getitem__, __setitem__, __delitem__, - __iter__, and __len__. - - """ - - @abstractmethod - def __setitem__(self, key, value): - raise KeyError - - @abstractmethod - def __delitem__(self, key): - raise KeyError - - __marker = object() - - def pop(self, key, default=__marker): - '''D.pop(k[,d]) -> v, remove specified key and return the corresponding value. - If key is not found, d is returned if given, otherwise KeyError is raised. - ''' - try: - value = self[key] - except KeyError: - if default is self.__marker: - raise - return default - else: - del self[key] - return value - - def popitem(self): - '''D.popitem() -> (k, v), remove and return some (key, value) pair - as a 2-tuple; but raise KeyError if D is empty. - ''' - try: - key = next(iter(self)) - except StopIteration: - raise KeyError from None - value = self[key] - del self[key] - return key, value - - def clear(self): - 'D.clear() -> None. Remove all items from D.' - try: - while True: - self.popitem() - except KeyError: - pass - - def update(self, other=(), /, **kwds): - ''' D.update([E, ]**F) -> None. Update D from mapping/iterable E and F. - If E present and has a .keys() method, does: for k in E: D[k] = E[k] - If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v - In either case, this is followed by: for k, v in F.items(): D[k] = v - ''' - if isinstance(other, Mapping): - for key in other: - self[key] = other[key] - elif hasattr(other, "keys"): - for key in other.keys(): - self[key] = other[key] - else: - for key, value in other: - self[key] = value - for key, value in kwds.items(): - self[key] = value - - def setdefault(self, key, default=None): - 'D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D' - try: - return self[key] - except KeyError: - self[key] = default - return default - -MutableMapping.register(dict) - - -### SEQUENCES ### - - -class Sequence(Reversible, Collection): - - """All the operations on a read-only sequence. - - Concrete subclasses must override __new__ or __init__, - __getitem__, and __len__. - """ - - __slots__ = () - - @abstractmethod - def __getitem__(self, index): - raise IndexError - - def __iter__(self): - i = 0 - try: - while True: - v = self[i] - yield v - i += 1 - except IndexError: - return - - def __contains__(self, value): - for v in self: - if v is value or v == value: - return True - return False - - def __reversed__(self): - for i in reversed(range(len(self))): - yield self[i] - - def index(self, value, start=0, stop=None): - '''S.index(value, [start, [stop]]) -> integer -- return first index of value. - Raises ValueError if the value is not present. - - Supporting start and stop arguments is optional, but - recommended. - ''' - if start is not None and start < 0: - start = max(len(self) + start, 0) - if stop is not None and stop < 0: - stop += len(self) - - i = start - while stop is None or i < stop: - try: - v = self[i] - if v is value or v == value: - return i - except IndexError: - break - i += 1 - raise ValueError - - def count(self, value): - 'S.count(value) -> integer -- return number of occurrences of value' - return sum(1 for v in self if v is value or v == value) - -Sequence.register(tuple) -Sequence.register(str) -Sequence.register(range) -Sequence.register(memoryview) - - -class ByteString(Sequence): - - """This unifies bytes and bytearray. - - XXX Should add all their methods. - """ - - __slots__ = () - -ByteString.register(bytes) -ByteString.register(bytearray) - - -class MutableSequence(Sequence): - - __slots__ = () - - """All the operations on a read-write sequence. - - Concrete subclasses must provide __new__ or __init__, - __getitem__, __setitem__, __delitem__, __len__, and insert(). - - """ - - @abstractmethod - def __setitem__(self, index, value): - raise IndexError - - @abstractmethod - def __delitem__(self, index): - raise IndexError - - @abstractmethod - def insert(self, index, value): - 'S.insert(index, value) -- insert value before index' - raise IndexError - - def append(self, value): - 'S.append(value) -- append value to the end of the sequence' - self.insert(len(self), value) - - def clear(self): - 'S.clear() -> None -- remove all items from S' - try: - while True: - self.pop() - except IndexError: - pass - - def reverse(self): - 'S.reverse() -- reverse *IN PLACE*' - n = len(self) - for i in range(n//2): - self[i], self[n-i-1] = self[n-i-1], self[i] - - def extend(self, values): - 'S.extend(iterable) -- extend sequence by appending elements from the iterable' - if values is self: - values = list(values) - for v in values: - self.append(v) - - def pop(self, index=-1): - '''S.pop([index]) -> item -- remove and return item at index (default last). - Raise IndexError if list is empty or index is out of range. - ''' - v = self[index] - del self[index] - return v - - def remove(self, value): - '''S.remove(value) -- remove first occurrence of value. - Raise ValueError if the value is not present. - ''' - del self[self.index(value)] - - def __iadd__(self, values): - self.extend(values) - return self - -MutableSequence.register(list) -MutableSequence.register(bytearray) # Multiply inheriting, see ByteString diff --git a/dist/lib/_compat_pickle.py b/dist/lib/_compat_pickle.py deleted file mode 100644 index f68496a..0000000 --- a/dist/lib/_compat_pickle.py +++ /dev/null @@ -1,251 +0,0 @@ -# This module is used to map the old Python 2 names to the new names used in -# Python 3 for the pickle module. This needed to make pickle streams -# generated with Python 2 loadable by Python 3. - -# This is a copy of lib2to3.fixes.fix_imports.MAPPING. We cannot import -# lib2to3 and use the mapping defined there, because lib2to3 uses pickle. -# Thus, this could cause the module to be imported recursively. -IMPORT_MAPPING = { - '__builtin__' : 'builtins', - 'copy_reg': 'copyreg', - 'Queue': 'queue', - 'SocketServer': 'socketserver', - 'ConfigParser': 'configparser', - 'repr': 'reprlib', - 'tkFileDialog': 'tkinter.filedialog', - 'tkSimpleDialog': 'tkinter.simpledialog', - 'tkColorChooser': 'tkinter.colorchooser', - 'tkCommonDialog': 'tkinter.commondialog', - 'Dialog': 'tkinter.dialog', - 'Tkdnd': 'tkinter.dnd', - 'tkFont': 'tkinter.font', - 'tkMessageBox': 'tkinter.messagebox', - 'ScrolledText': 'tkinter.scrolledtext', - 'Tkconstants': 'tkinter.constants', - 'Tix': 'tkinter.tix', - 'ttk': 'tkinter.ttk', - 'Tkinter': 'tkinter', - 'markupbase': '_markupbase', - '_winreg': 'winreg', - 'thread': '_thread', - 'dummy_thread': '_dummy_thread', - 'dbhash': 'dbm.bsd', - 'dumbdbm': 'dbm.dumb', - 'dbm': 'dbm.ndbm', - 'gdbm': 'dbm.gnu', - 'xmlrpclib': 'xmlrpc.client', - 'SimpleXMLRPCServer': 'xmlrpc.server', - 'httplib': 'http.client', - 'htmlentitydefs' : 'html.entities', - 'HTMLParser' : 'html.parser', - 'Cookie': 'http.cookies', - 'cookielib': 'http.cookiejar', - 'BaseHTTPServer': 'http.server', - 'test.test_support': 'test.support', - 'commands': 'subprocess', - 'urlparse' : 'urllib.parse', - 'robotparser' : 'urllib.robotparser', - 'urllib2': 'urllib.request', - 'anydbm': 'dbm', - '_abcoll' : 'collections.abc', -} - - -# This contains rename rules that are easy to handle. We ignore the more -# complex stuff (e.g. mapping the names in the urllib and types modules). -# These rules should be run before import names are fixed. -NAME_MAPPING = { - ('__builtin__', 'xrange'): ('builtins', 'range'), - ('__builtin__', 'reduce'): ('functools', 'reduce'), - ('__builtin__', 'intern'): ('sys', 'intern'), - ('__builtin__', 'unichr'): ('builtins', 'chr'), - ('__builtin__', 'unicode'): ('builtins', 'str'), - ('__builtin__', 'long'): ('builtins', 'int'), - ('itertools', 'izip'): ('builtins', 'zip'), - ('itertools', 'imap'): ('builtins', 'map'), - ('itertools', 'ifilter'): ('builtins', 'filter'), - ('itertools', 'ifilterfalse'): ('itertools', 'filterfalse'), - ('itertools', 'izip_longest'): ('itertools', 'zip_longest'), - ('UserDict', 'IterableUserDict'): ('collections', 'UserDict'), - ('UserList', 'UserList'): ('collections', 'UserList'), - ('UserString', 'UserString'): ('collections', 'UserString'), - ('whichdb', 'whichdb'): ('dbm', 'whichdb'), - ('_socket', 'fromfd'): ('socket', 'fromfd'), - ('_multiprocessing', 'Connection'): ('multiprocessing.connection', 'Connection'), - ('multiprocessing.process', 'Process'): ('multiprocessing.context', 'Process'), - ('multiprocessing.forking', 'Popen'): ('multiprocessing.popen_fork', 'Popen'), - ('urllib', 'ContentTooShortError'): ('urllib.error', 'ContentTooShortError'), - ('urllib', 'getproxies'): ('urllib.request', 'getproxies'), - ('urllib', 'pathname2url'): ('urllib.request', 'pathname2url'), - ('urllib', 'quote_plus'): ('urllib.parse', 'quote_plus'), - ('urllib', 'quote'): ('urllib.parse', 'quote'), - ('urllib', 'unquote_plus'): ('urllib.parse', 'unquote_plus'), - ('urllib', 'unquote'): ('urllib.parse', 'unquote'), - ('urllib', 'url2pathname'): ('urllib.request', 'url2pathname'), - ('urllib', 'urlcleanup'): ('urllib.request', 'urlcleanup'), - ('urllib', 'urlencode'): ('urllib.parse', 'urlencode'), - ('urllib', 'urlopen'): ('urllib.request', 'urlopen'), - ('urllib', 'urlretrieve'): ('urllib.request', 'urlretrieve'), - ('urllib2', 'HTTPError'): ('urllib.error', 'HTTPError'), - ('urllib2', 'URLError'): ('urllib.error', 'URLError'), -} - -PYTHON2_EXCEPTIONS = ( - "ArithmeticError", - "AssertionError", - "AttributeError", - "BaseException", - "BufferError", - "BytesWarning", - "DeprecationWarning", - "EOFError", - "EnvironmentError", - "Exception", - "FloatingPointError", - "FutureWarning", - "GeneratorExit", - "IOError", - "ImportError", - "ImportWarning", - "IndentationError", - "IndexError", - "KeyError", - "KeyboardInterrupt", - "LookupError", - "MemoryError", - "NameError", - "NotImplementedError", - "OSError", - "OverflowError", - "PendingDeprecationWarning", - "ReferenceError", - "RuntimeError", - "RuntimeWarning", - # StandardError is gone in Python 3, so we map it to Exception - "StopIteration", - "SyntaxError", - "SyntaxWarning", - "SystemError", - "SystemExit", - "TabError", - "TypeError", - "UnboundLocalError", - "UnicodeDecodeError", - "UnicodeEncodeError", - "UnicodeError", - "UnicodeTranslateError", - "UnicodeWarning", - "UserWarning", - "ValueError", - "Warning", - "ZeroDivisionError", -) - -try: - WindowsError -except NameError: - pass -else: - PYTHON2_EXCEPTIONS += ("WindowsError",) - -for excname in PYTHON2_EXCEPTIONS: - NAME_MAPPING[("exceptions", excname)] = ("builtins", excname) - -MULTIPROCESSING_EXCEPTIONS = ( - 'AuthenticationError', - 'BufferTooShort', - 'ProcessError', - 'TimeoutError', -) - -for excname in MULTIPROCESSING_EXCEPTIONS: - NAME_MAPPING[("multiprocessing", excname)] = ("multiprocessing.context", excname) - -# Same, but for 3.x to 2.x -REVERSE_IMPORT_MAPPING = dict((v, k) for (k, v) in IMPORT_MAPPING.items()) -assert len(REVERSE_IMPORT_MAPPING) == len(IMPORT_MAPPING) -REVERSE_NAME_MAPPING = dict((v, k) for (k, v) in NAME_MAPPING.items()) -assert len(REVERSE_NAME_MAPPING) == len(NAME_MAPPING) - -# Non-mutual mappings. - -IMPORT_MAPPING.update({ - 'cPickle': 'pickle', - '_elementtree': 'xml.etree.ElementTree', - 'FileDialog': 'tkinter.filedialog', - 'SimpleDialog': 'tkinter.simpledialog', - 'DocXMLRPCServer': 'xmlrpc.server', - 'SimpleHTTPServer': 'http.server', - 'CGIHTTPServer': 'http.server', - # For compatibility with broken pickles saved in old Python 3 versions - 'UserDict': 'collections', - 'UserList': 'collections', - 'UserString': 'collections', - 'whichdb': 'dbm', - 'StringIO': 'io', - 'cStringIO': 'io', -}) - -REVERSE_IMPORT_MAPPING.update({ - '_bz2': 'bz2', - '_dbm': 'dbm', - '_functools': 'functools', - '_gdbm': 'gdbm', - '_pickle': 'pickle', -}) - -NAME_MAPPING.update({ - ('__builtin__', 'basestring'): ('builtins', 'str'), - ('exceptions', 'StandardError'): ('builtins', 'Exception'), - ('UserDict', 'UserDict'): ('collections', 'UserDict'), - ('socket', '_socketobject'): ('socket', 'SocketType'), -}) - -REVERSE_NAME_MAPPING.update({ - ('_functools', 'reduce'): ('__builtin__', 'reduce'), - ('tkinter.filedialog', 'FileDialog'): ('FileDialog', 'FileDialog'), - ('tkinter.filedialog', 'LoadFileDialog'): ('FileDialog', 'LoadFileDialog'), - ('tkinter.filedialog', 'SaveFileDialog'): ('FileDialog', 'SaveFileDialog'), - ('tkinter.simpledialog', 'SimpleDialog'): ('SimpleDialog', 'SimpleDialog'), - ('xmlrpc.server', 'ServerHTMLDoc'): ('DocXMLRPCServer', 'ServerHTMLDoc'), - ('xmlrpc.server', 'XMLRPCDocGenerator'): - ('DocXMLRPCServer', 'XMLRPCDocGenerator'), - ('xmlrpc.server', 'DocXMLRPCRequestHandler'): - ('DocXMLRPCServer', 'DocXMLRPCRequestHandler'), - ('xmlrpc.server', 'DocXMLRPCServer'): - ('DocXMLRPCServer', 'DocXMLRPCServer'), - ('xmlrpc.server', 'DocCGIXMLRPCRequestHandler'): - ('DocXMLRPCServer', 'DocCGIXMLRPCRequestHandler'), - ('http.server', 'SimpleHTTPRequestHandler'): - ('SimpleHTTPServer', 'SimpleHTTPRequestHandler'), - ('http.server', 'CGIHTTPRequestHandler'): - ('CGIHTTPServer', 'CGIHTTPRequestHandler'), - ('_socket', 'socket'): ('socket', '_socketobject'), -}) - -PYTHON3_OSERROR_EXCEPTIONS = ( - 'BrokenPipeError', - 'ChildProcessError', - 'ConnectionAbortedError', - 'ConnectionError', - 'ConnectionRefusedError', - 'ConnectionResetError', - 'FileExistsError', - 'FileNotFoundError', - 'InterruptedError', - 'IsADirectoryError', - 'NotADirectoryError', - 'PermissionError', - 'ProcessLookupError', - 'TimeoutError', -) - -for excname in PYTHON3_OSERROR_EXCEPTIONS: - REVERSE_NAME_MAPPING[('builtins', excname)] = ('exceptions', 'OSError') - -PYTHON3_IMPORTERROR_EXCEPTIONS = ( - 'ModuleNotFoundError', -) - -for excname in PYTHON3_IMPORTERROR_EXCEPTIONS: - REVERSE_NAME_MAPPING[('builtins', excname)] = ('exceptions', 'ImportError') diff --git a/dist/lib/_compression.py b/dist/lib/_compression.py deleted file mode 100644 index b00f31b..0000000 --- a/dist/lib/_compression.py +++ /dev/null @@ -1,152 +0,0 @@ -"""Internal classes used by the gzip, lzma and bz2 modules""" - -import io - - -BUFFER_SIZE = io.DEFAULT_BUFFER_SIZE # Compressed data read chunk size - - -class BaseStream(io.BufferedIOBase): - """Mode-checking helper functions.""" - - def _check_not_closed(self): - if self.closed: - raise ValueError("I/O operation on closed file") - - def _check_can_read(self): - if not self.readable(): - raise io.UnsupportedOperation("File not open for reading") - - def _check_can_write(self): - if not self.writable(): - raise io.UnsupportedOperation("File not open for writing") - - def _check_can_seek(self): - if not self.readable(): - raise io.UnsupportedOperation("Seeking is only supported " - "on files open for reading") - if not self.seekable(): - raise io.UnsupportedOperation("The underlying file object " - "does not support seeking") - - -class DecompressReader(io.RawIOBase): - """Adapts the decompressor API to a RawIOBase reader API""" - - def readable(self): - return True - - def __init__(self, fp, decomp_factory, trailing_error=(), **decomp_args): - self._fp = fp - self._eof = False - self._pos = 0 # Current offset in decompressed stream - - # Set to size of decompressed stream once it is known, for SEEK_END - self._size = -1 - - # Save the decompressor factory and arguments. - # If the file contains multiple compressed streams, each - # stream will need a separate decompressor object. A new decompressor - # object is also needed when implementing a backwards seek(). - self._decomp_factory = decomp_factory - self._decomp_args = decomp_args - self._decompressor = self._decomp_factory(**self._decomp_args) - - # Exception class to catch from decompressor signifying invalid - # trailing data to ignore - self._trailing_error = trailing_error - - def close(self): - self._decompressor = None - return super().close() - - def seekable(self): - return self._fp.seekable() - - def readinto(self, b): - with memoryview(b) as view, view.cast("B") as byte_view: - data = self.read(len(byte_view)) - byte_view[:len(data)] = data - return len(data) - - def read(self, size=-1): - if size < 0: - return self.readall() - - if not size or self._eof: - return b"" - data = None # Default if EOF is encountered - # Depending on the input data, our call to the decompressor may not - # return any data. In this case, try again after reading another block. - while True: - if self._decompressor.eof: - rawblock = (self._decompressor.unused_data or - self._fp.read(BUFFER_SIZE)) - if not rawblock: - break - # Continue to next stream. - self._decompressor = self._decomp_factory( - **self._decomp_args) - try: - data = self._decompressor.decompress(rawblock, size) - except self._trailing_error: - # Trailing data isn't a valid compressed stream; ignore it. - break - else: - if self._decompressor.needs_input: - rawblock = self._fp.read(BUFFER_SIZE) - if not rawblock: - raise EOFError("Compressed file ended before the " - "end-of-stream marker was reached") - else: - rawblock = b"" - data = self._decompressor.decompress(rawblock, size) - if data: - break - if not data: - self._eof = True - self._size = self._pos - return b"" - self._pos += len(data) - return data - - # Rewind the file to the beginning of the data stream. - def _rewind(self): - self._fp.seek(0) - self._eof = False - self._pos = 0 - self._decompressor = self._decomp_factory(**self._decomp_args) - - def seek(self, offset, whence=io.SEEK_SET): - # Recalculate offset as an absolute file position. - if whence == io.SEEK_SET: - pass - elif whence == io.SEEK_CUR: - offset = self._pos + offset - elif whence == io.SEEK_END: - # Seeking relative to EOF - we need to know the file's size. - if self._size < 0: - while self.read(io.DEFAULT_BUFFER_SIZE): - pass - offset = self._size + offset - else: - raise ValueError("Invalid value for whence: {}".format(whence)) - - # Make it so that offset is the number of bytes to skip forward. - if offset < self._pos: - self._rewind() - else: - offset -= self._pos - - # Read and discard data until we reach the desired position. - while offset > 0: - data = self.read(min(io.DEFAULT_BUFFER_SIZE, offset)) - if not data: - break - offset -= len(data) - - return self._pos - - def tell(self): - """Return the current file position.""" - return self._pos diff --git a/dist/lib/_dummy_thread.py b/dist/lib/_dummy_thread.py deleted file mode 100644 index 2e46a07..0000000 --- a/dist/lib/_dummy_thread.py +++ /dev/null @@ -1,193 +0,0 @@ -"""Drop-in replacement for the thread module. - -Meant to be used as a brain-dead substitute so that threaded code does -not need to be rewritten for when the thread module is not present. - -Suggested usage is:: - - try: - import _thread - except ImportError: - import _dummy_thread as _thread - -""" -# Exports only things specified by thread documentation; -# skipping obsolete synonyms allocate(), start_new(), exit_thread(). -__all__ = ['error', 'start_new_thread', 'exit', 'get_ident', 'allocate_lock', - 'interrupt_main', 'LockType', 'RLock'] - -# A dummy value -TIMEOUT_MAX = 2**31 - -# NOTE: this module can be imported early in the extension building process, -# and so top level imports of other modules should be avoided. Instead, all -# imports are done when needed on a function-by-function basis. Since threads -# are disabled, the import lock should not be an issue anyway (??). - -error = RuntimeError - -def start_new_thread(function, args, kwargs={}): - """Dummy implementation of _thread.start_new_thread(). - - Compatibility is maintained by making sure that ``args`` is a - tuple and ``kwargs`` is a dictionary. If an exception is raised - and it is SystemExit (which can be done by _thread.exit()) it is - caught and nothing is done; all other exceptions are printed out - by using traceback.print_exc(). - - If the executed function calls interrupt_main the KeyboardInterrupt will be - raised when the function returns. - - """ - if type(args) != type(tuple()): - raise TypeError("2nd arg must be a tuple") - if type(kwargs) != type(dict()): - raise TypeError("3rd arg must be a dict") - global _main - _main = False - try: - function(*args, **kwargs) - except SystemExit: - pass - except: - import traceback - traceback.print_exc() - _main = True - global _interrupt - if _interrupt: - _interrupt = False - raise KeyboardInterrupt - -def exit(): - """Dummy implementation of _thread.exit().""" - raise SystemExit - -def get_ident(): - """Dummy implementation of _thread.get_ident(). - - Since this module should only be used when _threadmodule is not - available, it is safe to assume that the current process is the - only thread. Thus a constant can be safely returned. - """ - return 1 - -def allocate_lock(): - """Dummy implementation of _thread.allocate_lock().""" - return LockType() - -def stack_size(size=None): - """Dummy implementation of _thread.stack_size().""" - if size is not None: - raise error("setting thread stack size not supported") - return 0 - -def _set_sentinel(): - """Dummy implementation of _thread._set_sentinel().""" - return LockType() - -class LockType(object): - """Class implementing dummy implementation of _thread.LockType. - - Compatibility is maintained by maintaining self.locked_status - which is a boolean that stores the state of the lock. Pickling of - the lock, though, should not be done since if the _thread module is - then used with an unpickled ``lock()`` from here problems could - occur from this class not having atomic methods. - - """ - - def __init__(self): - self.locked_status = False - - def acquire(self, waitflag=None, timeout=-1): - """Dummy implementation of acquire(). - - For blocking calls, self.locked_status is automatically set to - True and returned appropriately based on value of - ``waitflag``. If it is non-blocking, then the value is - actually checked and not set if it is already acquired. This - is all done so that threading.Condition's assert statements - aren't triggered and throw a little fit. - - """ - if waitflag is None or waitflag: - self.locked_status = True - return True - else: - if not self.locked_status: - self.locked_status = True - return True - else: - if timeout > 0: - import time - time.sleep(timeout) - return False - - __enter__ = acquire - - def __exit__(self, typ, val, tb): - self.release() - - def release(self): - """Release the dummy lock.""" - # XXX Perhaps shouldn't actually bother to test? Could lead - # to problems for complex, threaded code. - if not self.locked_status: - raise error - self.locked_status = False - return True - - def locked(self): - return self.locked_status - - def __repr__(self): - return "<%s %s.%s object at %s>" % ( - "locked" if self.locked_status else "unlocked", - self.__class__.__module__, - self.__class__.__qualname__, - hex(id(self)) - ) - - -class RLock(LockType): - """Dummy implementation of threading._RLock. - - Re-entrant lock can be aquired multiple times and needs to be released - just as many times. This dummy implemention does not check wheter the - current thread actually owns the lock, but does accounting on the call - counts. - """ - def __init__(self): - super().__init__() - self._levels = 0 - - def acquire(self, waitflag=None, timeout=-1): - """Aquire the lock, can be called multiple times in succession. - """ - locked = super().acquire(waitflag, timeout) - if locked: - self._levels += 1 - return locked - - def release(self): - """Release needs to be called once for every call to acquire(). - """ - if self._levels == 0: - raise error - if self._levels == 1: - super().release() - self._levels -= 1 - -# Used to signal that interrupt_main was called in a "thread" -_interrupt = False -# True when not executing in a "thread" -_main = True - -def interrupt_main(): - """Set _interrupt flag to True to have start_new_thread raise - KeyboardInterrupt upon exiting.""" - if _main: - raise KeyboardInterrupt - else: - global _interrupt - _interrupt = True diff --git a/dist/lib/_markupbase.py b/dist/lib/_markupbase.py deleted file mode 100644 index 2af5f1c..0000000 --- a/dist/lib/_markupbase.py +++ /dev/null @@ -1,395 +0,0 @@ -"""Shared support for scanning document type declarations in HTML and XHTML. - -This module is used as a foundation for the html.parser module. It has no -documented public API and should not be used directly. - -""" - -import re - -_declname_match = re.compile(r'[a-zA-Z][-_.a-zA-Z0-9]*\s*').match -_declstringlit_match = re.compile(r'(\'[^\']*\'|"[^"]*")\s*').match -_commentclose = re.compile(r'--\s*>') -_markedsectionclose = re.compile(r']\s*]\s*>') - -# An analysis of the MS-Word extensions is available at -# http://www.planetpublish.com/xmlarena/xap/Thursday/WordtoXML.pdf - -_msmarkedsectionclose = re.compile(r']\s*>') - -del re - - -class ParserBase: - """Parser base class which provides some common support methods used - by the SGML/HTML and XHTML parsers.""" - - def __init__(self): - if self.__class__ is ParserBase: - raise RuntimeError( - "_markupbase.ParserBase must be subclassed") - - def error(self, message): - raise NotImplementedError( - "subclasses of ParserBase must override error()") - - def reset(self): - self.lineno = 1 - self.offset = 0 - - def getpos(self): - """Return current line number and offset.""" - return self.lineno, self.offset - - # Internal -- update line number and offset. This should be - # called for each piece of data exactly once, in order -- in other - # words the concatenation of all the input strings to this - # function should be exactly the entire input. - def updatepos(self, i, j): - if i >= j: - return j - rawdata = self.rawdata - nlines = rawdata.count("\n", i, j) - if nlines: - self.lineno = self.lineno + nlines - pos = rawdata.rindex("\n", i, j) # Should not fail - self.offset = j-(pos+1) - else: - self.offset = self.offset + j-i - return j - - _decl_otherchars = '' - - # Internal -- parse declaration (for use by subclasses). - def parse_declaration(self, i): - # This is some sort of declaration; in "HTML as - # deployed," this should only be the document type - # declaration (""). - # ISO 8879:1986, however, has more complex - # declaration syntax for elements in , including: - # --comment-- - # [marked section] - # name in the following list: ENTITY, DOCTYPE, ELEMENT, - # ATTLIST, NOTATION, SHORTREF, USEMAP, - # LINKTYPE, LINK, IDLINK, USELINK, SYSTEM - rawdata = self.rawdata - j = i + 2 - assert rawdata[i:j] == "": - # the empty comment - return j + 1 - if rawdata[j:j+1] in ("-", ""): - # Start of comment followed by buffer boundary, - # or just a buffer boundary. - return -1 - # A simple, practical version could look like: ((name|stringlit) S*) + '>' - n = len(rawdata) - if rawdata[j:j+2] == '--': #comment - # Locate --.*-- as the body of the comment - return self.parse_comment(i) - elif rawdata[j] == '[': #marked section - # Locate [statusWord [...arbitrary SGML...]] as the body of the marked section - # Where statusWord is one of TEMP, CDATA, IGNORE, INCLUDE, RCDATA - # Note that this is extended by Microsoft Office "Save as Web" function - # to include [if...] and [endif]. - return self.parse_marked_section(i) - else: #all other declaration elements - decltype, j = self._scan_name(j, i) - if j < 0: - return j - if decltype == "doctype": - self._decl_otherchars = '' - while j < n: - c = rawdata[j] - if c == ">": - # end of declaration syntax - data = rawdata[i+2:j] - if decltype == "doctype": - self.handle_decl(data) - else: - # According to the HTML5 specs sections "8.2.4.44 Bogus - # comment state" and "8.2.4.45 Markup declaration open - # state", a comment token should be emitted. - # Calling unknown_decl provides more flexibility though. - self.unknown_decl(data) - return j + 1 - if c in "\"'": - m = _declstringlit_match(rawdata, j) - if not m: - return -1 # incomplete - j = m.end() - elif c in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ": - name, j = self._scan_name(j, i) - elif c in self._decl_otherchars: - j = j + 1 - elif c == "[": - # this could be handled in a separate doctype parser - if decltype == "doctype": - j = self._parse_doctype_subset(j + 1, i) - elif decltype in {"attlist", "linktype", "link", "element"}: - # must tolerate []'d groups in a content model in an element declaration - # also in data attribute specifications of attlist declaration - # also link type declaration subsets in linktype declarations - # also link attribute specification lists in link declarations - self.error("unsupported '[' char in %s declaration" % decltype) - else: - self.error("unexpected '[' char in declaration") - else: - self.error( - "unexpected %r char in declaration" % rawdata[j]) - if j < 0: - return j - return -1 # incomplete - - # Internal -- parse a marked section - # Override this to handle MS-word extension syntax content - def parse_marked_section(self, i, report=1): - rawdata= self.rawdata - assert rawdata[i:i+3] == ' ending - match= _markedsectionclose.search(rawdata, i+3) - elif sectName in {"if", "else", "endif"}: - # look for MS Office ]> ending - match= _msmarkedsectionclose.search(rawdata, i+3) - else: - self.error('unknown status keyword %r in marked section' % rawdata[i+3:j]) - if not match: - return -1 - if report: - j = match.start(0) - self.unknown_decl(rawdata[i+3: j]) - return match.end(0) - - # Internal -- parse comment, return length or -1 if not terminated - def parse_comment(self, i, report=1): - rawdata = self.rawdata - if rawdata[i:i+4] != ' - --> --> - - ''' - -__UNDEF__ = [] # a special sentinel object -def small(text): - if text: - return '' + text + '' - else: - return '' - -def strong(text): - if text: - return '' + text + '' - else: - return '' - -def grey(text): - if text: - return '' + text + '' - else: - return '' - -def lookup(name, frame, locals): - """Find the value for a given name in the given environment.""" - if name in locals: - return 'local', locals[name] - if name in frame.f_globals: - return 'global', frame.f_globals[name] - if '__builtins__' in frame.f_globals: - builtins = frame.f_globals['__builtins__'] - if type(builtins) is type({}): - if name in builtins: - return 'builtin', builtins[name] - else: - if hasattr(builtins, name): - return 'builtin', getattr(builtins, name) - return None, __UNDEF__ - -def scanvars(reader, frame, locals): - """Scan one logical line of Python and look up values of variables used.""" - vars, lasttoken, parent, prefix, value = [], None, None, '', __UNDEF__ - for ttype, token, start, end, line in tokenize.generate_tokens(reader): - if ttype == tokenize.NEWLINE: break - if ttype == tokenize.NAME and token not in keyword.kwlist: - if lasttoken == '.': - if parent is not __UNDEF__: - value = getattr(parent, token, __UNDEF__) - vars.append((prefix + token, prefix, value)) - else: - where, value = lookup(token, frame, locals) - vars.append((token, where, value)) - elif token == '.': - prefix += lasttoken + '.' - parent = value - else: - parent, prefix = None, '' - lasttoken = token - return vars - -def html(einfo, context=5): - """Return a nice HTML document describing a given traceback.""" - etype, evalue, etb = einfo - if isinstance(etype, type): - etype = etype.__name__ - pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable - date = time.ctime(time.time()) - head = '' + pydoc.html.heading( - '%s' % - strong(pydoc.html.escape(str(etype))), - '#ffffff', '#6622aa', pyver + '
' + date) + ''' -

A problem occurred in a Python script. Here is the sequence of -function calls leading up to the error, in the order they occurred.

''' - - indent = '' + small(' ' * 5) + ' ' - frames = [] - records = inspect.getinnerframes(etb, context) - for frame, file, lnum, func, lines, index in records: - if file: - file = os.path.abspath(file) - link = '%s' % (file, pydoc.html.escape(file)) - else: - file = link = '?' - args, varargs, varkw, locals = inspect.getargvalues(frame) - call = '' - if func != '?': - call = 'in ' + strong(pydoc.html.escape(func)) - if func != "": - call += inspect.formatargvalues(args, varargs, varkw, locals, - formatvalue=lambda value: '=' + pydoc.html.repr(value)) - - highlight = {} - def reader(lnum=[lnum]): - highlight[lnum[0]] = 1 - try: return linecache.getline(file, lnum[0]) - finally: lnum[0] += 1 - vars = scanvars(reader, frame, locals) - - rows = ['%s%s %s' % - (' ', link, call)] - if index is not None: - i = lnum - index - for line in lines: - num = small(' ' * (5-len(str(i))) + str(i)) + ' ' - if i in highlight: - line = '=>%s%s' % (num, pydoc.html.preformat(line)) - rows.append('%s' % line) - else: - line = '  %s%s' % (num, pydoc.html.preformat(line)) - rows.append('%s' % grey(line)) - i += 1 - - done, dump = {}, [] - for name, where, value in vars: - if name in done: continue - done[name] = 1 - if value is not __UNDEF__: - if where in ('global', 'builtin'): - name = ('%s ' % where) + strong(name) - elif where == 'local': - name = strong(name) - else: - name = where + strong(name.split('.')[-1]) - dump.append('%s = %s' % (name, pydoc.html.repr(value))) - else: - dump.append(name + ' undefined') - - rows.append('%s' % small(grey(', '.join(dump)))) - frames.append(''' - -%s
''' % '\n'.join(rows)) - - exception = ['

%s: %s' % (strong(pydoc.html.escape(str(etype))), - pydoc.html.escape(str(evalue)))] - for name in dir(evalue): - if name[:1] == '_': continue - value = pydoc.html.repr(getattr(evalue, name)) - exception.append('\n
%s%s =\n%s' % (indent, name, value)) - - return head + ''.join(frames) + ''.join(exception) + ''' - - - -''' % pydoc.html.escape( - ''.join(traceback.format_exception(etype, evalue, etb))) - -def text(einfo, context=5): - """Return a plain text document describing a given traceback.""" - etype, evalue, etb = einfo - if isinstance(etype, type): - etype = etype.__name__ - pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable - date = time.ctime(time.time()) - head = "%s\n%s\n%s\n" % (str(etype), pyver, date) + ''' -A problem occurred in a Python script. Here is the sequence of -function calls leading up to the error, in the order they occurred. -''' - - frames = [] - records = inspect.getinnerframes(etb, context) - for frame, file, lnum, func, lines, index in records: - file = file and os.path.abspath(file) or '?' - args, varargs, varkw, locals = inspect.getargvalues(frame) - call = '' - if func != '?': - call = 'in ' + func - if func != "": - call += inspect.formatargvalues(args, varargs, varkw, locals, - formatvalue=lambda value: '=' + pydoc.text.repr(value)) - - highlight = {} - def reader(lnum=[lnum]): - highlight[lnum[0]] = 1 - try: return linecache.getline(file, lnum[0]) - finally: lnum[0] += 1 - vars = scanvars(reader, frame, locals) - - rows = [' %s %s' % (file, call)] - if index is not None: - i = lnum - index - for line in lines: - num = '%5d ' % i - rows.append(num+line.rstrip()) - i += 1 - - done, dump = {}, [] - for name, where, value in vars: - if name in done: continue - done[name] = 1 - if value is not __UNDEF__: - if where == 'global': name = 'global ' + name - elif where != 'local': name = where + name.split('.')[-1] - dump.append('%s = %s' % (name, pydoc.text.repr(value))) - else: - dump.append(name + ' undefined') - - rows.append('\n'.join(dump)) - frames.append('\n%s\n' % '\n'.join(rows)) - - exception = ['%s: %s' % (str(etype), str(evalue))] - for name in dir(evalue): - value = pydoc.text.repr(getattr(evalue, name)) - exception.append('\n%s%s = %s' % (" "*4, name, value)) - - return head + ''.join(frames) + ''.join(exception) + ''' - -The above is a description of an error in a Python program. Here is -the original traceback: - -%s -''' % ''.join(traceback.format_exception(etype, evalue, etb)) - -class Hook: - """A hook to replace sys.excepthook that shows tracebacks in HTML.""" - - def __init__(self, display=1, logdir=None, context=5, file=None, - format="html"): - self.display = display # send tracebacks to browser if true - self.logdir = logdir # log tracebacks to files if not None - self.context = context # number of source code lines per frame - self.file = file or sys.stdout # place to send the output - self.format = format - - def __call__(self, etype, evalue, etb): - self.handle((etype, evalue, etb)) - - def handle(self, info=None): - info = info or sys.exc_info() - if self.format == "html": - self.file.write(reset()) - - formatter = (self.format=="html") and html or text - plain = False - try: - doc = formatter(info, self.context) - except: # just in case something goes wrong - doc = ''.join(traceback.format_exception(*info)) - plain = True - - if self.display: - if plain: - doc = pydoc.html.escape(doc) - self.file.write('

' + doc + '
\n') - else: - self.file.write(doc + '\n') - else: - self.file.write('

A problem occurred in a Python script.\n') - - if self.logdir is not None: - suffix = ['.txt', '.html'][self.format=="html"] - (fd, path) = tempfile.mkstemp(suffix=suffix, dir=self.logdir) - - try: - with os.fdopen(fd, 'w') as file: - file.write(doc) - msg = '%s contains the description of this error.' % path - except: - msg = 'Tried to save traceback to %s, but failed.' % path - - if self.format == 'html': - self.file.write('

%s

\n' % msg) - else: - self.file.write(msg + '\n') - try: - self.file.flush() - except: pass - -handler = Hook().handle -def enable(display=1, logdir=None, context=5, format="html"): - """Install an exception handler that formats tracebacks as HTML. - - The optional argument 'display' can be set to 0 to suppress sending the - traceback to the browser, and 'logdir' can be set to a directory to cause - tracebacks to be written to files there.""" - sys.excepthook = Hook(display=display, logdir=logdir, - context=context, format=format) diff --git a/dist/lib/chunk.py b/dist/lib/chunk.py deleted file mode 100644 index 870c39f..0000000 --- a/dist/lib/chunk.py +++ /dev/null @@ -1,169 +0,0 @@ -"""Simple class to read IFF chunks. - -An IFF chunk (used in formats such as AIFF, TIFF, RMFF (RealMedia File -Format)) has the following structure: - -+----------------+ -| ID (4 bytes) | -+----------------+ -| size (4 bytes) | -+----------------+ -| data | -| ... | -+----------------+ - -The ID is a 4-byte string which identifies the type of chunk. - -The size field (a 32-bit value, encoded using big-endian byte order) -gives the size of the whole chunk, including the 8-byte header. - -Usually an IFF-type file consists of one or more chunks. The proposed -usage of the Chunk class defined here is to instantiate an instance at -the start of each chunk and read from the instance until it reaches -the end, after which a new instance can be instantiated. At the end -of the file, creating a new instance will fail with an EOFError -exception. - -Usage: -while True: - try: - chunk = Chunk(file) - except EOFError: - break - chunktype = chunk.getname() - while True: - data = chunk.read(nbytes) - if not data: - pass - # do something with data - -The interface is file-like. The implemented methods are: -read, close, seek, tell, isatty. -Extra methods are: skip() (called by close, skips to the end of the chunk), -getname() (returns the name (ID) of the chunk) - -The __init__ method has one required argument, a file-like object -(including a chunk instance), and one optional argument, a flag which -specifies whether or not chunks are aligned on 2-byte boundaries. The -default is 1, i.e. aligned. -""" - -class Chunk: - def __init__(self, file, align=True, bigendian=True, inclheader=False): - import struct - self.closed = False - self.align = align # whether to align to word (2-byte) boundaries - if bigendian: - strflag = '>' - else: - strflag = '<' - self.file = file - self.chunkname = file.read(4) - if len(self.chunkname) < 4: - raise EOFError - try: - self.chunksize = struct.unpack_from(strflag+'L', file.read(4))[0] - except struct.error: - raise EOFError from None - if inclheader: - self.chunksize = self.chunksize - 8 # subtract header - self.size_read = 0 - try: - self.offset = self.file.tell() - except (AttributeError, OSError): - self.seekable = False - else: - self.seekable = True - - def getname(self): - """Return the name (ID) of the current chunk.""" - return self.chunkname - - def getsize(self): - """Return the size of the current chunk.""" - return self.chunksize - - def close(self): - if not self.closed: - try: - self.skip() - finally: - self.closed = True - - def isatty(self): - if self.closed: - raise ValueError("I/O operation on closed file") - return False - - def seek(self, pos, whence=0): - """Seek to specified position into the chunk. - Default position is 0 (start of chunk). - If the file is not seekable, this will result in an error. - """ - - if self.closed: - raise ValueError("I/O operation on closed file") - if not self.seekable: - raise OSError("cannot seek") - if whence == 1: - pos = pos + self.size_read - elif whence == 2: - pos = pos + self.chunksize - if pos < 0 or pos > self.chunksize: - raise RuntimeError - self.file.seek(self.offset + pos, 0) - self.size_read = pos - - def tell(self): - if self.closed: - raise ValueError("I/O operation on closed file") - return self.size_read - - def read(self, size=-1): - """Read at most size bytes from the chunk. - If size is omitted or negative, read until the end - of the chunk. - """ - - if self.closed: - raise ValueError("I/O operation on closed file") - if self.size_read >= self.chunksize: - return b'' - if size < 0: - size = self.chunksize - self.size_read - if size > self.chunksize - self.size_read: - size = self.chunksize - self.size_read - data = self.file.read(size) - self.size_read = self.size_read + len(data) - if self.size_read == self.chunksize and \ - self.align and \ - (self.chunksize & 1): - dummy = self.file.read(1) - self.size_read = self.size_read + len(dummy) - return data - - def skip(self): - """Skip the rest of the chunk. - If you are not interested in the contents of the chunk, - this method should be called so that the file points to - the start of the next chunk. - """ - - if self.closed: - raise ValueError("I/O operation on closed file") - if self.seekable: - try: - n = self.chunksize - self.size_read - # maybe fix alignment - if self.align and (self.chunksize & 1): - n = n + 1 - self.file.seek(n, 1) - self.size_read = self.size_read + n - return - except OSError: - pass - while self.size_read < self.chunksize: - n = min(8192, self.chunksize - self.size_read) - dummy = self.read(n) - if not dummy: - raise EOFError diff --git a/dist/lib/cmd.py b/dist/lib/cmd.py deleted file mode 100644 index 859e910..0000000 --- a/dist/lib/cmd.py +++ /dev/null @@ -1,401 +0,0 @@ -"""A generic class to build line-oriented command interpreters. - -Interpreters constructed with this class obey the following conventions: - -1. End of file on input is processed as the command 'EOF'. -2. A command is parsed out of each line by collecting the prefix composed - of characters in the identchars member. -3. A command `foo' is dispatched to a method 'do_foo()'; the do_ method - is passed a single argument consisting of the remainder of the line. -4. Typing an empty line repeats the last command. (Actually, it calls the - method `emptyline', which may be overridden in a subclass.) -5. There is a predefined `help' method. Given an argument `topic', it - calls the command `help_topic'. With no arguments, it lists all topics - with defined help_ functions, broken into up to three topics; documented - commands, miscellaneous help topics, and undocumented commands. -6. The command '?' is a synonym for `help'. The command '!' is a synonym - for `shell', if a do_shell method exists. -7. If completion is enabled, completing commands will be done automatically, - and completing of commands args is done by calling complete_foo() with - arguments text, line, begidx, endidx. text is string we are matching - against, all returned matches must begin with it. line is the current - input line (lstripped), begidx and endidx are the beginning and end - indexes of the text being matched, which could be used to provide - different completion depending upon which position the argument is in. - -The `default' method may be overridden to intercept commands for which there -is no do_ method. - -The `completedefault' method may be overridden to intercept completions for -commands that have no complete_ method. - -The data member `self.ruler' sets the character used to draw separator lines -in the help messages. If empty, no ruler line is drawn. It defaults to "=". - -If the value of `self.intro' is nonempty when the cmdloop method is called, -it is printed out on interpreter startup. This value may be overridden -via an optional argument to the cmdloop() method. - -The data members `self.doc_header', `self.misc_header', and -`self.undoc_header' set the headers used for the help function's -listings of documented functions, miscellaneous topics, and undocumented -functions respectively. -""" - -import string, sys - -__all__ = ["Cmd"] - -PROMPT = '(Cmd) ' -IDENTCHARS = string.ascii_letters + string.digits + '_' - -class Cmd: - """A simple framework for writing line-oriented command interpreters. - - These are often useful for test harnesses, administrative tools, and - prototypes that will later be wrapped in a more sophisticated interface. - - A Cmd instance or subclass instance is a line-oriented interpreter - framework. There is no good reason to instantiate Cmd itself; rather, - it's useful as a superclass of an interpreter class you define yourself - in order to inherit Cmd's methods and encapsulate action methods. - - """ - prompt = PROMPT - identchars = IDENTCHARS - ruler = '=' - lastcmd = '' - intro = None - doc_leader = "" - doc_header = "Documented commands (type help ):" - misc_header = "Miscellaneous help topics:" - undoc_header = "Undocumented commands:" - nohelp = "*** No help on %s" - use_rawinput = 1 - - def __init__(self, completekey='tab', stdin=None, stdout=None): - """Instantiate a line-oriented interpreter framework. - - The optional argument 'completekey' is the readline name of a - completion key; it defaults to the Tab key. If completekey is - not None and the readline module is available, command completion - is done automatically. The optional arguments stdin and stdout - specify alternate input and output file objects; if not specified, - sys.stdin and sys.stdout are used. - - """ - if stdin is not None: - self.stdin = stdin - else: - self.stdin = sys.stdin - if stdout is not None: - self.stdout = stdout - else: - self.stdout = sys.stdout - self.cmdqueue = [] - self.completekey = completekey - - def cmdloop(self, intro=None): - """Repeatedly issue a prompt, accept input, parse an initial prefix - off the received input, and dispatch to action methods, passing them - the remainder of the line as argument. - - """ - - self.preloop() - if self.use_rawinput and self.completekey: - try: - import readline - self.old_completer = readline.get_completer() - readline.set_completer(self.complete) - readline.parse_and_bind(self.completekey+": complete") - except ImportError: - pass - try: - if intro is not None: - self.intro = intro - if self.intro: - self.stdout.write(str(self.intro)+"\n") - stop = None - while not stop: - if self.cmdqueue: - line = self.cmdqueue.pop(0) - else: - if self.use_rawinput: - try: - line = input(self.prompt) - except EOFError: - line = 'EOF' - else: - self.stdout.write(self.prompt) - self.stdout.flush() - line = self.stdin.readline() - if not len(line): - line = 'EOF' - else: - line = line.rstrip('\r\n') - line = self.precmd(line) - stop = self.onecmd(line) - stop = self.postcmd(stop, line) - self.postloop() - finally: - if self.use_rawinput and self.completekey: - try: - import readline - readline.set_completer(self.old_completer) - except ImportError: - pass - - - def precmd(self, line): - """Hook method executed just before the command line is - interpreted, but after the input prompt is generated and issued. - - """ - return line - - def postcmd(self, stop, line): - """Hook method executed just after a command dispatch is finished.""" - return stop - - def preloop(self): - """Hook method executed once when the cmdloop() method is called.""" - pass - - def postloop(self): - """Hook method executed once when the cmdloop() method is about to - return. - - """ - pass - - def parseline(self, line): - """Parse the line into a command name and a string containing - the arguments. Returns a tuple containing (command, args, line). - 'command' and 'args' may be None if the line couldn't be parsed. - """ - line = line.strip() - if not line: - return None, None, line - elif line[0] == '?': - line = 'help ' + line[1:] - elif line[0] == '!': - if hasattr(self, 'do_shell'): - line = 'shell ' + line[1:] - else: - return None, None, line - i, n = 0, len(line) - while i < n and line[i] in self.identchars: i = i+1 - cmd, arg = line[:i], line[i:].strip() - return cmd, arg, line - - def onecmd(self, line): - """Interpret the argument as though it had been typed in response - to the prompt. - - This may be overridden, but should not normally need to be; - see the precmd() and postcmd() methods for useful execution hooks. - The return value is a flag indicating whether interpretation of - commands by the interpreter should stop. - - """ - cmd, arg, line = self.parseline(line) - if not line: - return self.emptyline() - if cmd is None: - return self.default(line) - self.lastcmd = line - if line == 'EOF' : - self.lastcmd = '' - if cmd == '': - return self.default(line) - else: - try: - func = getattr(self, 'do_' + cmd) - except AttributeError: - return self.default(line) - return func(arg) - - def emptyline(self): - """Called when an empty line is entered in response to the prompt. - - If this method is not overridden, it repeats the last nonempty - command entered. - - """ - if self.lastcmd: - return self.onecmd(self.lastcmd) - - def default(self, line): - """Called on an input line when the command prefix is not recognized. - - If this method is not overridden, it prints an error message and - returns. - - """ - self.stdout.write('*** Unknown syntax: %s\n'%line) - - def completedefault(self, *ignored): - """Method called to complete an input line when no command-specific - complete_*() method is available. - - By default, it returns an empty list. - - """ - return [] - - def completenames(self, text, *ignored): - dotext = 'do_'+text - return [a[3:] for a in self.get_names() if a.startswith(dotext)] - - def complete(self, text, state): - """Return the next possible completion for 'text'. - - If a command has not been entered, then complete against command list. - Otherwise try to call complete_ to get list of completions. - """ - if state == 0: - import readline - origline = readline.get_line_buffer() - line = origline.lstrip() - stripped = len(origline) - len(line) - begidx = readline.get_begidx() - stripped - endidx = readline.get_endidx() - stripped - if begidx>0: - cmd, args, foo = self.parseline(line) - if cmd == '': - compfunc = self.completedefault - else: - try: - compfunc = getattr(self, 'complete_' + cmd) - except AttributeError: - compfunc = self.completedefault - else: - compfunc = self.completenames - self.completion_matches = compfunc(text, line, begidx, endidx) - try: - return self.completion_matches[state] - except IndexError: - return None - - def get_names(self): - # This method used to pull in base class attributes - # at a time dir() didn't do it yet. - return dir(self.__class__) - - def complete_help(self, *args): - commands = set(self.completenames(*args)) - topics = set(a[5:] for a in self.get_names() - if a.startswith('help_' + args[0])) - return list(commands | topics) - - def do_help(self, arg): - 'List available commands with "help" or detailed help with "help cmd".' - if arg: - # XXX check arg syntax - try: - func = getattr(self, 'help_' + arg) - except AttributeError: - try: - doc=getattr(self, 'do_' + arg).__doc__ - if doc: - self.stdout.write("%s\n"%str(doc)) - return - except AttributeError: - pass - self.stdout.write("%s\n"%str(self.nohelp % (arg,))) - return - func() - else: - names = self.get_names() - cmds_doc = [] - cmds_undoc = [] - help = {} - for name in names: - if name[:5] == 'help_': - help[name[5:]]=1 - names.sort() - # There can be duplicates if routines overridden - prevname = '' - for name in names: - if name[:3] == 'do_': - if name == prevname: - continue - prevname = name - cmd=name[3:] - if cmd in help: - cmds_doc.append(cmd) - del help[cmd] - elif getattr(self, name).__doc__: - cmds_doc.append(cmd) - else: - cmds_undoc.append(cmd) - self.stdout.write("%s\n"%str(self.doc_leader)) - self.print_topics(self.doc_header, cmds_doc, 15,80) - self.print_topics(self.misc_header, list(help.keys()),15,80) - self.print_topics(self.undoc_header, cmds_undoc, 15,80) - - def print_topics(self, header, cmds, cmdlen, maxcol): - if cmds: - self.stdout.write("%s\n"%str(header)) - if self.ruler: - self.stdout.write("%s\n"%str(self.ruler * len(header))) - self.columnize(cmds, maxcol-1) - self.stdout.write("\n") - - def columnize(self, list, displaywidth=80): - """Display a list of strings as a compact set of columns. - - Each column is only as wide as necessary. - Columns are separated by two spaces (one was not legible enough). - """ - if not list: - self.stdout.write("\n") - return - - nonstrings = [i for i in range(len(list)) - if not isinstance(list[i], str)] - if nonstrings: - raise TypeError("list[i] not a string for i in %s" - % ", ".join(map(str, nonstrings))) - size = len(list) - if size == 1: - self.stdout.write('%s\n'%str(list[0])) - return - # Try every row count from 1 upwards - for nrows in range(1, len(list)): - ncols = (size+nrows-1) // nrows - colwidths = [] - totwidth = -2 - for col in range(ncols): - colwidth = 0 - for row in range(nrows): - i = row + nrows*col - if i >= size: - break - x = list[i] - colwidth = max(colwidth, len(x)) - colwidths.append(colwidth) - totwidth += colwidth + 2 - if totwidth > displaywidth: - break - if totwidth <= displaywidth: - break - else: - nrows = len(list) - ncols = 1 - colwidths = [0] - for row in range(nrows): - texts = [] - for col in range(ncols): - i = row + nrows*col - if i >= size: - x = "" - else: - x = list[i] - texts.append(x) - while texts and not texts[-1]: - del texts[-1] - for col in range(len(texts)): - texts[col] = texts[col].ljust(colwidths[col]) - self.stdout.write("%s\n"%str(" ".join(texts))) diff --git a/dist/lib/code.py b/dist/lib/code.py deleted file mode 100644 index 76000f8..0000000 --- a/dist/lib/code.py +++ /dev/null @@ -1,315 +0,0 @@ -"""Utilities needed to emulate Python's interactive interpreter. - -""" - -# Inspired by similar code by Jeff Epler and Fredrik Lundh. - - -import sys -import traceback -from codeop import CommandCompiler, compile_command - -__all__ = ["InteractiveInterpreter", "InteractiveConsole", "interact", - "compile_command"] - -class InteractiveInterpreter: - """Base class for InteractiveConsole. - - This class deals with parsing and interpreter state (the user's - namespace); it doesn't deal with input buffering or prompting or - input file naming (the filename is always passed in explicitly). - - """ - - def __init__(self, locals=None): - """Constructor. - - The optional 'locals' argument specifies the dictionary in - which code will be executed; it defaults to a newly created - dictionary with key "__name__" set to "__console__" and key - "__doc__" set to None. - - """ - if locals is None: - locals = {"__name__": "__console__", "__doc__": None} - self.locals = locals - self.compile = CommandCompiler() - - def runsource(self, source, filename="", symbol="single"): - """Compile and run some source in the interpreter. - - Arguments are as for compile_command(). - - One of several things can happen: - - 1) The input is incorrect; compile_command() raised an - exception (SyntaxError or OverflowError). A syntax traceback - will be printed by calling the showsyntaxerror() method. - - 2) The input is incomplete, and more input is required; - compile_command() returned None. Nothing happens. - - 3) The input is complete; compile_command() returned a code - object. The code is executed by calling self.runcode() (which - also handles run-time exceptions, except for SystemExit). - - The return value is True in case 2, False in the other cases (unless - an exception is raised). The return value can be used to - decide whether to use sys.ps1 or sys.ps2 to prompt the next - line. - - """ - try: - code = self.compile(source, filename, symbol) - except (OverflowError, SyntaxError, ValueError): - # Case 1 - self.showsyntaxerror(filename) - return False - - if code is None: - # Case 2 - return True - - # Case 3 - self.runcode(code) - return False - - def runcode(self, code): - """Execute a code object. - - When an exception occurs, self.showtraceback() is called to - display a traceback. All exceptions are caught except - SystemExit, which is reraised. - - A note about KeyboardInterrupt: this exception may occur - elsewhere in this code, and may not always be caught. The - caller should be prepared to deal with it. - - """ - try: - exec(code, self.locals) - except SystemExit: - raise - except: - self.showtraceback() - - def showsyntaxerror(self, filename=None): - """Display the syntax error that just occurred. - - This doesn't display a stack trace because there isn't one. - - If a filename is given, it is stuffed in the exception instead - of what was there before (because Python's parser always uses - "" when reading from a string). - - The output is written by self.write(), below. - - """ - type, value, tb = sys.exc_info() - sys.last_type = type - sys.last_value = value - sys.last_traceback = tb - if filename and type is SyntaxError: - # Work hard to stuff the correct filename in the exception - try: - msg, (dummy_filename, lineno, offset, line) = value.args - except ValueError: - # Not the format we expect; leave it alone - pass - else: - # Stuff in the right filename - value = SyntaxError(msg, (filename, lineno, offset, line)) - sys.last_value = value - if sys.excepthook is sys.__excepthook__: - lines = traceback.format_exception_only(type, value) - self.write(''.join(lines)) - else: - # If someone has set sys.excepthook, we let that take precedence - # over self.write - sys.excepthook(type, value, tb) - - def showtraceback(self): - """Display the exception that just occurred. - - We remove the first stack item because it is our own code. - - The output is written by self.write(), below. - - """ - sys.last_type, sys.last_value, last_tb = ei = sys.exc_info() - sys.last_traceback = last_tb - try: - lines = traceback.format_exception(ei[0], ei[1], last_tb.tb_next) - if sys.excepthook is sys.__excepthook__: - self.write(''.join(lines)) - else: - # If someone has set sys.excepthook, we let that take precedence - # over self.write - sys.excepthook(ei[0], ei[1], last_tb) - finally: - last_tb = ei = None - - def write(self, data): - """Write a string. - - The base implementation writes to sys.stderr; a subclass may - replace this with a different implementation. - - """ - sys.stderr.write(data) - - -class InteractiveConsole(InteractiveInterpreter): - """Closely emulate the behavior of the interactive Python interpreter. - - This class builds on InteractiveInterpreter and adds prompting - using the familiar sys.ps1 and sys.ps2, and input buffering. - - """ - - def __init__(self, locals=None, filename=""): - """Constructor. - - The optional locals argument will be passed to the - InteractiveInterpreter base class. - - The optional filename argument should specify the (file)name - of the input stream; it will show up in tracebacks. - - """ - InteractiveInterpreter.__init__(self, locals) - self.filename = filename - self.resetbuffer() - - def resetbuffer(self): - """Reset the input buffer.""" - self.buffer = [] - - def interact(self, banner=None, exitmsg=None): - """Closely emulate the interactive Python console. - - The optional banner argument specifies the banner to print - before the first interaction; by default it prints a banner - similar to the one printed by the real Python interpreter, - followed by the current class name in parentheses (so as not - to confuse this with the real interpreter -- since it's so - close!). - - The optional exitmsg argument specifies the exit message - printed when exiting. Pass the empty string to suppress - printing an exit message. If exitmsg is not given or None, - a default message is printed. - - """ - try: - sys.ps1 - except AttributeError: - sys.ps1 = ">>> " - try: - sys.ps2 - except AttributeError: - sys.ps2 = "... " - cprt = 'Type "help", "copyright", "credits" or "license" for more information.' - if banner is None: - self.write("Python %s on %s\n%s\n(%s)\n" % - (sys.version, sys.platform, cprt, - self.__class__.__name__)) - elif banner: - self.write("%s\n" % str(banner)) - more = 0 - while 1: - try: - if more: - prompt = sys.ps2 - else: - prompt = sys.ps1 - try: - line = self.raw_input(prompt) - except EOFError: - self.write("\n") - break - else: - more = self.push(line) - except KeyboardInterrupt: - self.write("\nKeyboardInterrupt\n") - self.resetbuffer() - more = 0 - if exitmsg is None: - self.write('now exiting %s...\n' % self.__class__.__name__) - elif exitmsg != '': - self.write('%s\n' % exitmsg) - - def push(self, line): - """Push a line to the interpreter. - - The line should not have a trailing newline; it may have - internal newlines. The line is appended to a buffer and the - interpreter's runsource() method is called with the - concatenated contents of the buffer as source. If this - indicates that the command was executed or invalid, the buffer - is reset; otherwise, the command is incomplete, and the buffer - is left as it was after the line was appended. The return - value is 1 if more input is required, 0 if the line was dealt - with in some way (this is the same as runsource()). - - """ - self.buffer.append(line) - source = "\n".join(self.buffer) - more = self.runsource(source, self.filename) - if not more: - self.resetbuffer() - return more - - def raw_input(self, prompt=""): - """Write a prompt and read a line. - - The returned line does not include the trailing newline. - When the user enters the EOF key sequence, EOFError is raised. - - The base implementation uses the built-in function - input(); a subclass may replace this with a different - implementation. - - """ - return input(prompt) - - - -def interact(banner=None, readfunc=None, local=None, exitmsg=None): - """Closely emulate the interactive Python interpreter. - - This is a backwards compatible interface to the InteractiveConsole - class. When readfunc is not specified, it attempts to import the - readline module to enable GNU readline if it is available. - - Arguments (all optional, all default to None): - - banner -- passed to InteractiveConsole.interact() - readfunc -- if not None, replaces InteractiveConsole.raw_input() - local -- passed to InteractiveInterpreter.__init__() - exitmsg -- passed to InteractiveConsole.interact() - - """ - console = InteractiveConsole(local) - if readfunc is not None: - console.raw_input = readfunc - else: - try: - import readline - except ImportError: - pass - console.interact(banner, exitmsg) - - -if __name__ == "__main__": - import argparse - - parser = argparse.ArgumentParser() - parser.add_argument('-q', action='store_true', - help="don't print version and copyright messages") - args = parser.parse_args() - if args.q or sys.flags.quiet: - banner = '' - else: - banner = None - interact(banner) diff --git a/dist/lib/codecs.py b/dist/lib/codecs.py deleted file mode 100644 index 7f23e97..0000000 --- a/dist/lib/codecs.py +++ /dev/null @@ -1,1126 +0,0 @@ -""" codecs -- Python Codec Registry, API and helpers. - - -Written by Marc-Andre Lemburg (mal@lemburg.com). - -(c) Copyright CNRI, All Rights Reserved. NO WARRANTY. - -""" - -import builtins -import sys - -### Registry and builtin stateless codec functions - -try: - from _codecs import * -except ImportError as why: - raise SystemError('Failed to load the builtin codecs: %s' % why) - -__all__ = ["register", "lookup", "open", "EncodedFile", "BOM", "BOM_BE", - "BOM_LE", "BOM32_BE", "BOM32_LE", "BOM64_BE", "BOM64_LE", - "BOM_UTF8", "BOM_UTF16", "BOM_UTF16_LE", "BOM_UTF16_BE", - "BOM_UTF32", "BOM_UTF32_LE", "BOM_UTF32_BE", - "CodecInfo", "Codec", "IncrementalEncoder", "IncrementalDecoder", - "StreamReader", "StreamWriter", - "StreamReaderWriter", "StreamRecoder", - "getencoder", "getdecoder", "getincrementalencoder", - "getincrementaldecoder", "getreader", "getwriter", - "encode", "decode", "iterencode", "iterdecode", - "strict_errors", "ignore_errors", "replace_errors", - "xmlcharrefreplace_errors", - "backslashreplace_errors", "namereplace_errors", - "register_error", "lookup_error"] - -### Constants - -# -# Byte Order Mark (BOM = ZERO WIDTH NO-BREAK SPACE = U+FEFF) -# and its possible byte string values -# for UTF8/UTF16/UTF32 output and little/big endian machines -# - -# UTF-8 -BOM_UTF8 = b'\xef\xbb\xbf' - -# UTF-16, little endian -BOM_LE = BOM_UTF16_LE = b'\xff\xfe' - -# UTF-16, big endian -BOM_BE = BOM_UTF16_BE = b'\xfe\xff' - -# UTF-32, little endian -BOM_UTF32_LE = b'\xff\xfe\x00\x00' - -# UTF-32, big endian -BOM_UTF32_BE = b'\x00\x00\xfe\xff' - -if sys.byteorder == 'little': - - # UTF-16, native endianness - BOM = BOM_UTF16 = BOM_UTF16_LE - - # UTF-32, native endianness - BOM_UTF32 = BOM_UTF32_LE - -else: - - # UTF-16, native endianness - BOM = BOM_UTF16 = BOM_UTF16_BE - - # UTF-32, native endianness - BOM_UTF32 = BOM_UTF32_BE - -# Old broken names (don't use in new code) -BOM32_LE = BOM_UTF16_LE -BOM32_BE = BOM_UTF16_BE -BOM64_LE = BOM_UTF32_LE -BOM64_BE = BOM_UTF32_BE - - -### Codec base classes (defining the API) - -class CodecInfo(tuple): - """Codec details when looking up the codec registry""" - - # Private API to allow Python 3.4 to blacklist the known non-Unicode - # codecs in the standard library. A more general mechanism to - # reliably distinguish test encodings from other codecs will hopefully - # be defined for Python 3.5 - # - # See http://bugs.python.org/issue19619 - _is_text_encoding = True # Assume codecs are text encodings by default - - def __new__(cls, encode, decode, streamreader=None, streamwriter=None, - incrementalencoder=None, incrementaldecoder=None, name=None, - *, _is_text_encoding=None): - self = tuple.__new__(cls, (encode, decode, streamreader, streamwriter)) - self.name = name - self.encode = encode - self.decode = decode - self.incrementalencoder = incrementalencoder - self.incrementaldecoder = incrementaldecoder - self.streamwriter = streamwriter - self.streamreader = streamreader - if _is_text_encoding is not None: - self._is_text_encoding = _is_text_encoding - return self - - def __repr__(self): - return "<%s.%s object for encoding %s at %#x>" % \ - (self.__class__.__module__, self.__class__.__qualname__, - self.name, id(self)) - -class Codec: - - """ Defines the interface for stateless encoders/decoders. - - The .encode()/.decode() methods may use different error - handling schemes by providing the errors argument. These - string values are predefined: - - 'strict' - raise a ValueError error (or a subclass) - 'ignore' - ignore the character and continue with the next - 'replace' - replace with a suitable replacement character; - Python will use the official U+FFFD REPLACEMENT - CHARACTER for the builtin Unicode codecs on - decoding and '?' on encoding. - 'surrogateescape' - replace with private code points U+DCnn. - 'xmlcharrefreplace' - Replace with the appropriate XML - character reference (only for encoding). - 'backslashreplace' - Replace with backslashed escape sequences. - 'namereplace' - Replace with \\N{...} escape sequences - (only for encoding). - - The set of allowed values can be extended via register_error. - - """ - def encode(self, input, errors='strict'): - - """ Encodes the object input and returns a tuple (output - object, length consumed). - - errors defines the error handling to apply. It defaults to - 'strict' handling. - - The method may not store state in the Codec instance. Use - StreamWriter for codecs which have to keep state in order to - make encoding efficient. - - The encoder must be able to handle zero length input and - return an empty object of the output object type in this - situation. - - """ - raise NotImplementedError - - def decode(self, input, errors='strict'): - - """ Decodes the object input and returns a tuple (output - object, length consumed). - - input must be an object which provides the bf_getreadbuf - buffer slot. Python strings, buffer objects and memory - mapped files are examples of objects providing this slot. - - errors defines the error handling to apply. It defaults to - 'strict' handling. - - The method may not store state in the Codec instance. Use - StreamReader for codecs which have to keep state in order to - make decoding efficient. - - The decoder must be able to handle zero length input and - return an empty object of the output object type in this - situation. - - """ - raise NotImplementedError - -class IncrementalEncoder(object): - """ - An IncrementalEncoder encodes an input in multiple steps. The input can - be passed piece by piece to the encode() method. The IncrementalEncoder - remembers the state of the encoding process between calls to encode(). - """ - def __init__(self, errors='strict'): - """ - Creates an IncrementalEncoder instance. - - The IncrementalEncoder may use different error handling schemes by - providing the errors keyword argument. See the module docstring - for a list of possible values. - """ - self.errors = errors - self.buffer = "" - - def encode(self, input, final=False): - """ - Encodes input and returns the resulting object. - """ - raise NotImplementedError - - def reset(self): - """ - Resets the encoder to the initial state. - """ - - def getstate(self): - """ - Return the current state of the encoder. - """ - return 0 - - def setstate(self, state): - """ - Set the current state of the encoder. state must have been - returned by getstate(). - """ - -class BufferedIncrementalEncoder(IncrementalEncoder): - """ - This subclass of IncrementalEncoder can be used as the baseclass for an - incremental encoder if the encoder must keep some of the output in a - buffer between calls to encode(). - """ - def __init__(self, errors='strict'): - IncrementalEncoder.__init__(self, errors) - # unencoded input that is kept between calls to encode() - self.buffer = "" - - def _buffer_encode(self, input, errors, final): - # Overwrite this method in subclasses: It must encode input - # and return an (output, length consumed) tuple - raise NotImplementedError - - def encode(self, input, final=False): - # encode input (taking the buffer into account) - data = self.buffer + input - (result, consumed) = self._buffer_encode(data, self.errors, final) - # keep unencoded input until the next call - self.buffer = data[consumed:] - return result - - def reset(self): - IncrementalEncoder.reset(self) - self.buffer = "" - - def getstate(self): - return self.buffer or 0 - - def setstate(self, state): - self.buffer = state or "" - -class IncrementalDecoder(object): - """ - An IncrementalDecoder decodes an input in multiple steps. The input can - be passed piece by piece to the decode() method. The IncrementalDecoder - remembers the state of the decoding process between calls to decode(). - """ - def __init__(self, errors='strict'): - """ - Create an IncrementalDecoder instance. - - The IncrementalDecoder may use different error handling schemes by - providing the errors keyword argument. See the module docstring - for a list of possible values. - """ - self.errors = errors - - def decode(self, input, final=False): - """ - Decode input and returns the resulting object. - """ - raise NotImplementedError - - def reset(self): - """ - Reset the decoder to the initial state. - """ - - def getstate(self): - """ - Return the current state of the decoder. - - This must be a (buffered_input, additional_state_info) tuple. - buffered_input must be a bytes object containing bytes that - were passed to decode() that have not yet been converted. - additional_state_info must be a non-negative integer - representing the state of the decoder WITHOUT yet having - processed the contents of buffered_input. In the initial state - and after reset(), getstate() must return (b"", 0). - """ - return (b"", 0) - - def setstate(self, state): - """ - Set the current state of the decoder. - - state must have been returned by getstate(). The effect of - setstate((b"", 0)) must be equivalent to reset(). - """ - -class BufferedIncrementalDecoder(IncrementalDecoder): - """ - This subclass of IncrementalDecoder can be used as the baseclass for an - incremental decoder if the decoder must be able to handle incomplete - byte sequences. - """ - def __init__(self, errors='strict'): - IncrementalDecoder.__init__(self, errors) - # undecoded input that is kept between calls to decode() - self.buffer = b"" - - def _buffer_decode(self, input, errors, final): - # Overwrite this method in subclasses: It must decode input - # and return an (output, length consumed) tuple - raise NotImplementedError - - def decode(self, input, final=False): - # decode input (taking the buffer into account) - data = self.buffer + input - (result, consumed) = self._buffer_decode(data, self.errors, final) - # keep undecoded input until the next call - self.buffer = data[consumed:] - return result - - def reset(self): - IncrementalDecoder.reset(self) - self.buffer = b"" - - def getstate(self): - # additional state info is always 0 - return (self.buffer, 0) - - def setstate(self, state): - # ignore additional state info - self.buffer = state[0] - -# -# The StreamWriter and StreamReader class provide generic working -# interfaces which can be used to implement new encoding submodules -# very easily. See encodings/utf_8.py for an example on how this is -# done. -# - -class StreamWriter(Codec): - - def __init__(self, stream, errors='strict'): - - """ Creates a StreamWriter instance. - - stream must be a file-like object open for writing. - - The StreamWriter may use different error handling - schemes by providing the errors keyword argument. These - parameters are predefined: - - 'strict' - raise a ValueError (or a subclass) - 'ignore' - ignore the character and continue with the next - 'replace'- replace with a suitable replacement character - 'xmlcharrefreplace' - Replace with the appropriate XML - character reference. - 'backslashreplace' - Replace with backslashed escape - sequences. - 'namereplace' - Replace with \\N{...} escape sequences. - - The set of allowed parameter values can be extended via - register_error. - """ - self.stream = stream - self.errors = errors - - def write(self, object): - - """ Writes the object's contents encoded to self.stream. - """ - data, consumed = self.encode(object, self.errors) - self.stream.write(data) - - def writelines(self, list): - - """ Writes the concatenated list of strings to the stream - using .write(). - """ - self.write(''.join(list)) - - def reset(self): - - """ Flushes and resets the codec buffers used for keeping state. - - Calling this method should ensure that the data on the - output is put into a clean state, that allows appending - of new fresh data without having to rescan the whole - stream to recover state. - - """ - pass - - def seek(self, offset, whence=0): - self.stream.seek(offset, whence) - if whence == 0 and offset == 0: - self.reset() - - def __getattr__(self, name, - getattr=getattr): - - """ Inherit all other methods from the underlying stream. - """ - return getattr(self.stream, name) - - def __enter__(self): - return self - - def __exit__(self, type, value, tb): - self.stream.close() - -### - -class StreamReader(Codec): - - charbuffertype = str - - def __init__(self, stream, errors='strict'): - - """ Creates a StreamReader instance. - - stream must be a file-like object open for reading. - - The StreamReader may use different error handling - schemes by providing the errors keyword argument. These - parameters are predefined: - - 'strict' - raise a ValueError (or a subclass) - 'ignore' - ignore the character and continue with the next - 'replace'- replace with a suitable replacement character - 'backslashreplace' - Replace with backslashed escape sequences; - - The set of allowed parameter values can be extended via - register_error. - """ - self.stream = stream - self.errors = errors - self.bytebuffer = b"" - self._empty_charbuffer = self.charbuffertype() - self.charbuffer = self._empty_charbuffer - self.linebuffer = None - - def decode(self, input, errors='strict'): - raise NotImplementedError - - def read(self, size=-1, chars=-1, firstline=False): - - """ Decodes data from the stream self.stream and returns the - resulting object. - - chars indicates the number of decoded code points or bytes to - return. read() will never return more data than requested, - but it might return less, if there is not enough available. - - size indicates the approximate maximum number of decoded - bytes or code points to read for decoding. The decoder - can modify this setting as appropriate. The default value - -1 indicates to read and decode as much as possible. size - is intended to prevent having to decode huge files in one - step. - - If firstline is true, and a UnicodeDecodeError happens - after the first line terminator in the input only the first line - will be returned, the rest of the input will be kept until the - next call to read(). - - The method should use a greedy read strategy, meaning that - it should read as much data as is allowed within the - definition of the encoding and the given size, e.g. if - optional encoding endings or state markers are available - on the stream, these should be read too. - """ - # If we have lines cached, first merge them back into characters - if self.linebuffer: - self.charbuffer = self._empty_charbuffer.join(self.linebuffer) - self.linebuffer = None - - if chars < 0: - # For compatibility with other read() methods that take a - # single argument - chars = size - - # read until we get the required number of characters (if available) - while True: - # can the request be satisfied from the character buffer? - if chars >= 0: - if len(self.charbuffer) >= chars: - break - # we need more data - if size < 0: - newdata = self.stream.read() - else: - newdata = self.stream.read(size) - # decode bytes (those remaining from the last call included) - data = self.bytebuffer + newdata - if not data: - break - try: - newchars, decodedbytes = self.decode(data, self.errors) - except UnicodeDecodeError as exc: - if firstline: - newchars, decodedbytes = \ - self.decode(data[:exc.start], self.errors) - lines = newchars.splitlines(keepends=True) - if len(lines)<=1: - raise - else: - raise - # keep undecoded bytes until the next call - self.bytebuffer = data[decodedbytes:] - # put new characters in the character buffer - self.charbuffer += newchars - # there was no data available - if not newdata: - break - if chars < 0: - # Return everything we've got - result = self.charbuffer - self.charbuffer = self._empty_charbuffer - else: - # Return the first chars characters - result = self.charbuffer[:chars] - self.charbuffer = self.charbuffer[chars:] - return result - - def readline(self, size=None, keepends=True): - - """ Read one line from the input stream and return the - decoded data. - - size, if given, is passed as size argument to the - read() method. - - """ - # If we have lines cached from an earlier read, return - # them unconditionally - if self.linebuffer: - line = self.linebuffer[0] - del self.linebuffer[0] - if len(self.linebuffer) == 1: - # revert to charbuffer mode; we might need more data - # next time - self.charbuffer = self.linebuffer[0] - self.linebuffer = None - if not keepends: - line = line.splitlines(keepends=False)[0] - return line - - readsize = size or 72 - line = self._empty_charbuffer - # If size is given, we call read() only once - while True: - data = self.read(readsize, firstline=True) - if data: - # If we're at a "\r" read one extra character (which might - # be a "\n") to get a proper line ending. If the stream is - # temporarily exhausted we return the wrong line ending. - if (isinstance(data, str) and data.endswith("\r")) or \ - (isinstance(data, bytes) and data.endswith(b"\r")): - data += self.read(size=1, chars=1) - - line += data - lines = line.splitlines(keepends=True) - if lines: - if len(lines) > 1: - # More than one line result; the first line is a full line - # to return - line = lines[0] - del lines[0] - if len(lines) > 1: - # cache the remaining lines - lines[-1] += self.charbuffer - self.linebuffer = lines - self.charbuffer = None - else: - # only one remaining line, put it back into charbuffer - self.charbuffer = lines[0] + self.charbuffer - if not keepends: - line = line.splitlines(keepends=False)[0] - break - line0withend = lines[0] - line0withoutend = lines[0].splitlines(keepends=False)[0] - if line0withend != line0withoutend: # We really have a line end - # Put the rest back together and keep it until the next call - self.charbuffer = self._empty_charbuffer.join(lines[1:]) + \ - self.charbuffer - if keepends: - line = line0withend - else: - line = line0withoutend - break - # we didn't get anything or this was our only try - if not data or size is not None: - if line and not keepends: - line = line.splitlines(keepends=False)[0] - break - if readsize < 8000: - readsize *= 2 - return line - - def readlines(self, sizehint=None, keepends=True): - - """ Read all lines available on the input stream - and return them as a list. - - Line breaks are implemented using the codec's decoder - method and are included in the list entries. - - sizehint, if given, is ignored since there is no efficient - way to finding the true end-of-line. - - """ - data = self.read() - return data.splitlines(keepends) - - def reset(self): - - """ Resets the codec buffers used for keeping state. - - Note that no stream repositioning should take place. - This method is primarily intended to be able to recover - from decoding errors. - - """ - self.bytebuffer = b"" - self.charbuffer = self._empty_charbuffer - self.linebuffer = None - - def seek(self, offset, whence=0): - """ Set the input stream's current position. - - Resets the codec buffers used for keeping state. - """ - self.stream.seek(offset, whence) - self.reset() - - def __next__(self): - - """ Return the next decoded line from the input stream.""" - line = self.readline() - if line: - return line - raise StopIteration - - def __iter__(self): - return self - - def __getattr__(self, name, - getattr=getattr): - - """ Inherit all other methods from the underlying stream. - """ - return getattr(self.stream, name) - - def __enter__(self): - return self - - def __exit__(self, type, value, tb): - self.stream.close() - -### - -class StreamReaderWriter: - - """ StreamReaderWriter instances allow wrapping streams which - work in both read and write modes. - - The design is such that one can use the factory functions - returned by the codec.lookup() function to construct the - instance. - - """ - # Optional attributes set by the file wrappers below - encoding = 'unknown' - - def __init__(self, stream, Reader, Writer, errors='strict'): - - """ Creates a StreamReaderWriter instance. - - stream must be a Stream-like object. - - Reader, Writer must be factory functions or classes - providing the StreamReader, StreamWriter interface resp. - - Error handling is done in the same way as defined for the - StreamWriter/Readers. - - """ - self.stream = stream - self.reader = Reader(stream, errors) - self.writer = Writer(stream, errors) - self.errors = errors - - def read(self, size=-1): - - return self.reader.read(size) - - def readline(self, size=None): - - return self.reader.readline(size) - - def readlines(self, sizehint=None): - - return self.reader.readlines(sizehint) - - def __next__(self): - - """ Return the next decoded line from the input stream.""" - return next(self.reader) - - def __iter__(self): - return self - - def write(self, data): - - return self.writer.write(data) - - def writelines(self, list): - - return self.writer.writelines(list) - - def reset(self): - - self.reader.reset() - self.writer.reset() - - def seek(self, offset, whence=0): - self.stream.seek(offset, whence) - self.reader.reset() - if whence == 0 and offset == 0: - self.writer.reset() - - def __getattr__(self, name, - getattr=getattr): - - """ Inherit all other methods from the underlying stream. - """ - return getattr(self.stream, name) - - # these are needed to make "with StreamReaderWriter(...)" work properly - - def __enter__(self): - return self - - def __exit__(self, type, value, tb): - self.stream.close() - -### - -class StreamRecoder: - - """ StreamRecoder instances translate data from one encoding to another. - - They use the complete set of APIs returned by the - codecs.lookup() function to implement their task. - - Data written to the StreamRecoder is first decoded into an - intermediate format (depending on the "decode" codec) and then - written to the underlying stream using an instance of the provided - Writer class. - - In the other direction, data is read from the underlying stream using - a Reader instance and then encoded and returned to the caller. - - """ - # Optional attributes set by the file wrappers below - data_encoding = 'unknown' - file_encoding = 'unknown' - - def __init__(self, stream, encode, decode, Reader, Writer, - errors='strict'): - - """ Creates a StreamRecoder instance which implements a two-way - conversion: encode and decode work on the frontend (the - data visible to .read() and .write()) while Reader and Writer - work on the backend (the data in stream). - - You can use these objects to do transparent - transcodings from e.g. latin-1 to utf-8 and back. - - stream must be a file-like object. - - encode and decode must adhere to the Codec interface; Reader and - Writer must be factory functions or classes providing the - StreamReader and StreamWriter interfaces resp. - - Error handling is done in the same way as defined for the - StreamWriter/Readers. - - """ - self.stream = stream - self.encode = encode - self.decode = decode - self.reader = Reader(stream, errors) - self.writer = Writer(stream, errors) - self.errors = errors - - def read(self, size=-1): - - data = self.reader.read(size) - data, bytesencoded = self.encode(data, self.errors) - return data - - def readline(self, size=None): - - if size is None: - data = self.reader.readline() - else: - data = self.reader.readline(size) - data, bytesencoded = self.encode(data, self.errors) - return data - - def readlines(self, sizehint=None): - - data = self.reader.read() - data, bytesencoded = self.encode(data, self.errors) - return data.splitlines(keepends=True) - - def __next__(self): - - """ Return the next decoded line from the input stream.""" - data = next(self.reader) - data, bytesencoded = self.encode(data, self.errors) - return data - - def __iter__(self): - return self - - def write(self, data): - - data, bytesdecoded = self.decode(data, self.errors) - return self.writer.write(data) - - def writelines(self, list): - - data = b''.join(list) - data, bytesdecoded = self.decode(data, self.errors) - return self.writer.write(data) - - def reset(self): - - self.reader.reset() - self.writer.reset() - - def seek(self, offset, whence=0): - # Seeks must be propagated to both the readers and writers - # as they might need to reset their internal buffers. - self.reader.seek(offset, whence) - self.writer.seek(offset, whence) - - def __getattr__(self, name, - getattr=getattr): - - """ Inherit all other methods from the underlying stream. - """ - return getattr(self.stream, name) - - def __enter__(self): - return self - - def __exit__(self, type, value, tb): - self.stream.close() - -### Shortcuts - -def open(filename, mode='r', encoding=None, errors='strict', buffering=-1): - - """ Open an encoded file using the given mode and return - a wrapped version providing transparent encoding/decoding. - - Note: The wrapped version will only accept the object format - defined by the codecs, i.e. Unicode objects for most builtin - codecs. Output is also codec dependent and will usually be - Unicode as well. - - Underlying encoded files are always opened in binary mode. - The default file mode is 'r', meaning to open the file in read mode. - - encoding specifies the encoding which is to be used for the - file. - - errors may be given to define the error handling. It defaults - to 'strict' which causes ValueErrors to be raised in case an - encoding error occurs. - - buffering has the same meaning as for the builtin open() API. - It defaults to -1 which means that the default buffer size will - be used. - - The returned wrapped file object provides an extra attribute - .encoding which allows querying the used encoding. This - attribute is only available if an encoding was specified as - parameter. - - """ - if encoding is not None and \ - 'b' not in mode: - # Force opening of the file in binary mode - mode = mode + 'b' - file = builtins.open(filename, mode, buffering) - if encoding is None: - return file - - try: - info = lookup(encoding) - srw = StreamReaderWriter(file, info.streamreader, info.streamwriter, errors) - # Add attributes to simplify introspection - srw.encoding = encoding - return srw - except: - file.close() - raise - -def EncodedFile(file, data_encoding, file_encoding=None, errors='strict'): - - """ Return a wrapped version of file which provides transparent - encoding translation. - - Data written to the wrapped file is decoded according - to the given data_encoding and then encoded to the underlying - file using file_encoding. The intermediate data type - will usually be Unicode but depends on the specified codecs. - - Bytes read from the file are decoded using file_encoding and then - passed back to the caller encoded using data_encoding. - - If file_encoding is not given, it defaults to data_encoding. - - errors may be given to define the error handling. It defaults - to 'strict' which causes ValueErrors to be raised in case an - encoding error occurs. - - The returned wrapped file object provides two extra attributes - .data_encoding and .file_encoding which reflect the given - parameters of the same name. The attributes can be used for - introspection by Python programs. - - """ - if file_encoding is None: - file_encoding = data_encoding - data_info = lookup(data_encoding) - file_info = lookup(file_encoding) - sr = StreamRecoder(file, data_info.encode, data_info.decode, - file_info.streamreader, file_info.streamwriter, errors) - # Add attributes to simplify introspection - sr.data_encoding = data_encoding - sr.file_encoding = file_encoding - return sr - -### Helpers for codec lookup - -def getencoder(encoding): - - """ Lookup up the codec for the given encoding and return - its encoder function. - - Raises a LookupError in case the encoding cannot be found. - - """ - return lookup(encoding).encode - -def getdecoder(encoding): - - """ Lookup up the codec for the given encoding and return - its decoder function. - - Raises a LookupError in case the encoding cannot be found. - - """ - return lookup(encoding).decode - -def getincrementalencoder(encoding): - - """ Lookup up the codec for the given encoding and return - its IncrementalEncoder class or factory function. - - Raises a LookupError in case the encoding cannot be found - or the codecs doesn't provide an incremental encoder. - - """ - encoder = lookup(encoding).incrementalencoder - if encoder is None: - raise LookupError(encoding) - return encoder - -def getincrementaldecoder(encoding): - - """ Lookup up the codec for the given encoding and return - its IncrementalDecoder class or factory function. - - Raises a LookupError in case the encoding cannot be found - or the codecs doesn't provide an incremental decoder. - - """ - decoder = lookup(encoding).incrementaldecoder - if decoder is None: - raise LookupError(encoding) - return decoder - -def getreader(encoding): - - """ Lookup up the codec for the given encoding and return - its StreamReader class or factory function. - - Raises a LookupError in case the encoding cannot be found. - - """ - return lookup(encoding).streamreader - -def getwriter(encoding): - - """ Lookup up the codec for the given encoding and return - its StreamWriter class or factory function. - - Raises a LookupError in case the encoding cannot be found. - - """ - return lookup(encoding).streamwriter - -def iterencode(iterator, encoding, errors='strict', **kwargs): - """ - Encoding iterator. - - Encodes the input strings from the iterator using an IncrementalEncoder. - - errors and kwargs are passed through to the IncrementalEncoder - constructor. - """ - encoder = getincrementalencoder(encoding)(errors, **kwargs) - for input in iterator: - output = encoder.encode(input) - if output: - yield output - output = encoder.encode("", True) - if output: - yield output - -def iterdecode(iterator, encoding, errors='strict', **kwargs): - """ - Decoding iterator. - - Decodes the input strings from the iterator using an IncrementalDecoder. - - errors and kwargs are passed through to the IncrementalDecoder - constructor. - """ - decoder = getincrementaldecoder(encoding)(errors, **kwargs) - for input in iterator: - output = decoder.decode(input) - if output: - yield output - output = decoder.decode(b"", True) - if output: - yield output - -### Helpers for charmap-based codecs - -def make_identity_dict(rng): - - """ make_identity_dict(rng) -> dict - - Return a dictionary where elements of the rng sequence are - mapped to themselves. - - """ - return {i:i for i in rng} - -def make_encoding_map(decoding_map): - - """ Creates an encoding map from a decoding map. - - If a target mapping in the decoding map occurs multiple - times, then that target is mapped to None (undefined mapping), - causing an exception when encountered by the charmap codec - during translation. - - One example where this happens is cp875.py which decodes - multiple character to \\u001a. - - """ - m = {} - for k,v in decoding_map.items(): - if not v in m: - m[v] = k - else: - m[v] = None - return m - -### error handlers - -try: - strict_errors = lookup_error("strict") - ignore_errors = lookup_error("ignore") - replace_errors = lookup_error("replace") - xmlcharrefreplace_errors = lookup_error("xmlcharrefreplace") - backslashreplace_errors = lookup_error("backslashreplace") - namereplace_errors = lookup_error("namereplace") -except LookupError: - # In --disable-unicode builds, these error handler are missing - strict_errors = None - ignore_errors = None - replace_errors = None - xmlcharrefreplace_errors = None - backslashreplace_errors = None - namereplace_errors = None - -# Tell modulefinder that using codecs probably needs the encodings -# package -_false = 0 -if _false: - import encodings - -### Tests - -if __name__ == '__main__': - - # Make stdout translate Latin-1 output into UTF-8 output - sys.stdout = EncodedFile(sys.stdout, 'latin-1', 'utf-8') - - # Have stdin translate Latin-1 input into UTF-8 input - sys.stdin = EncodedFile(sys.stdin, 'utf-8', 'latin-1') diff --git a/dist/lib/codeop.py b/dist/lib/codeop.py deleted file mode 100644 index 3c2bb60..0000000 --- a/dist/lib/codeop.py +++ /dev/null @@ -1,176 +0,0 @@ -r"""Utilities to compile possibly incomplete Python source code. - -This module provides two interfaces, broadly similar to the builtin -function compile(), which take program text, a filename and a 'mode' -and: - -- Return code object if the command is complete and valid -- Return None if the command is incomplete -- Raise SyntaxError, ValueError or OverflowError if the command is a - syntax error (OverflowError and ValueError can be produced by - malformed literals). - -Approach: - -First, check if the source consists entirely of blank lines and -comments; if so, replace it with 'pass', because the built-in -parser doesn't always do the right thing for these. - -Compile three times: as is, with \n, and with \n\n appended. If it -compiles as is, it's complete. If it compiles with one \n appended, -we expect more. If it doesn't compile either way, we compare the -error we get when compiling with \n or \n\n appended. If the errors -are the same, the code is broken. But if the errors are different, we -expect more. Not intuitive; not even guaranteed to hold in future -releases; but this matches the compiler's behavior from Python 1.4 -through 2.2, at least. - -Caveat: - -It is possible (but not likely) that the parser stops parsing with a -successful outcome before reaching the end of the source; in this -case, trailing symbols may be ignored instead of causing an error. -For example, a backslash followed by two newlines may be followed by -arbitrary garbage. This will be fixed once the API for the parser is -better. - -The two interfaces are: - -compile_command(source, filename, symbol): - - Compiles a single command in the manner described above. - -CommandCompiler(): - - Instances of this class have __call__ methods identical in - signature to compile_command; the difference is that if the - instance compiles program text containing a __future__ statement, - the instance 'remembers' and compiles all subsequent program texts - with the statement in force. - -The module also provides another class: - -Compile(): - - Instances of this class act like the built-in function compile, - but with 'memory' in the sense described above. -""" - -import __future__ -import warnings - -_features = [getattr(__future__, fname) - for fname in __future__.all_feature_names] - -__all__ = ["compile_command", "Compile", "CommandCompiler"] - -PyCF_DONT_IMPLY_DEDENT = 0x200 # Matches pythonrun.h - -def _maybe_compile(compiler, source, filename, symbol): - # Check for source consisting of only blank lines and comments - for line in source.split("\n"): - line = line.strip() - if line and line[0] != '#': - break # Leave it alone - else: - if symbol != "eval": - source = "pass" # Replace it with a 'pass' statement - - err = err1 = err2 = None - code = code1 = code2 = None - - try: - code = compiler(source, filename, symbol) - except SyntaxError as err: - pass - - # Suppress warnings after the first compile to avoid duplication. - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - try: - code1 = compiler(source + "\n", filename, symbol) - except SyntaxError as e: - err1 = e - - try: - code2 = compiler(source + "\n\n", filename, symbol) - except SyntaxError as e: - err2 = e - - try: - if code: - return code - if not code1 and repr(err1) == repr(err2): - raise err1 - finally: - err1 = err2 = None - -def _compile(source, filename, symbol): - return compile(source, filename, symbol, PyCF_DONT_IMPLY_DEDENT) - -def compile_command(source, filename="", symbol="single"): - r"""Compile a command and determine whether it is incomplete. - - Arguments: - - source -- the source string; may contain \n characters - filename -- optional filename from which source was read; default - "" - symbol -- optional grammar start symbol; "single" (default), "exec" - or "eval" - - Return value / exceptions raised: - - - Return a code object if the command is complete and valid - - Return None if the command is incomplete - - Raise SyntaxError, ValueError or OverflowError if the command is a - syntax error (OverflowError and ValueError can be produced by - malformed literals). - """ - return _maybe_compile(_compile, source, filename, symbol) - -class Compile: - """Instances of this class behave much like the built-in compile - function, but if one is used to compile text containing a future - statement, it "remembers" and compiles all subsequent program texts - with the statement in force.""" - def __init__(self): - self.flags = PyCF_DONT_IMPLY_DEDENT - - def __call__(self, source, filename, symbol): - codeob = compile(source, filename, symbol, self.flags, 1) - for feature in _features: - if codeob.co_flags & feature.compiler_flag: - self.flags |= feature.compiler_flag - return codeob - -class CommandCompiler: - """Instances of this class have __call__ methods identical in - signature to compile_command; the difference is that if the - instance compiles program text containing a __future__ statement, - the instance 'remembers' and compiles all subsequent program texts - with the statement in force.""" - - def __init__(self,): - self.compiler = Compile() - - def __call__(self, source, filename="", symbol="single"): - r"""Compile a command and determine whether it is incomplete. - - Arguments: - - source -- the source string; may contain \n characters - filename -- optional filename from which source was read; - default "" - symbol -- optional grammar start symbol; "single" (default) or - "eval" - - Return value / exceptions raised: - - - Return a code object if the command is complete and valid - - Return None if the command is incomplete - - Raise SyntaxError, ValueError or OverflowError if the command is a - syntax error (OverflowError and ValueError can be produced by - malformed literals). - """ - return _maybe_compile(self.compiler, source, filename, symbol) diff --git a/dist/lib/collections/__init__.py b/dist/lib/collections/__init__.py deleted file mode 100644 index a78a47c..0000000 --- a/dist/lib/collections/__init__.py +++ /dev/null @@ -1,1279 +0,0 @@ -'''This module implements specialized container datatypes providing -alternatives to Python's general purpose built-in containers, dict, -list, set, and tuple. - -* namedtuple factory function for creating tuple subclasses with named fields -* deque list-like container with fast appends and pops on either end -* ChainMap dict-like class for creating a single view of multiple mappings -* Counter dict subclass for counting hashable objects -* OrderedDict dict subclass that remembers the order entries were added -* defaultdict dict subclass that calls a factory function to supply missing values -* UserDict wrapper around dictionary objects for easier dict subclassing -* UserList wrapper around list objects for easier list subclassing -* UserString wrapper around string objects for easier string subclassing - -''' - -__all__ = ['deque', 'defaultdict', 'namedtuple', 'UserDict', 'UserList', - 'UserString', 'Counter', 'OrderedDict', 'ChainMap'] - -import _collections_abc -from operator import itemgetter as _itemgetter, eq as _eq -from keyword import iskeyword as _iskeyword -import sys as _sys -import heapq as _heapq -from _weakref import proxy as _proxy -from itertools import repeat as _repeat, chain as _chain, starmap as _starmap -from reprlib import recursive_repr as _recursive_repr - -try: - from _collections import deque -except ImportError: - pass -else: - _collections_abc.MutableSequence.register(deque) - -try: - from _collections import defaultdict -except ImportError: - pass - - -def __getattr__(name): - # For backwards compatibility, continue to make the collections ABCs - # through Python 3.6 available through the collections module. - # Note, no new collections ABCs were added in Python 3.7 - if name in _collections_abc.__all__: - obj = getattr(_collections_abc, name) - import warnings - warnings.warn("Using or importing the ABCs from 'collections' instead " - "of from 'collections.abc' is deprecated since Python 3.3, " - "and in 3.9 it will stop working", - DeprecationWarning, stacklevel=2) - globals()[name] = obj - return obj - raise AttributeError(f'module {__name__!r} has no attribute {name!r}') - -################################################################################ -### OrderedDict -################################################################################ - -class _OrderedDictKeysView(_collections_abc.KeysView): - - def __reversed__(self): - yield from reversed(self._mapping) - -class _OrderedDictItemsView(_collections_abc.ItemsView): - - def __reversed__(self): - for key in reversed(self._mapping): - yield (key, self._mapping[key]) - -class _OrderedDictValuesView(_collections_abc.ValuesView): - - def __reversed__(self): - for key in reversed(self._mapping): - yield self._mapping[key] - -class _Link(object): - __slots__ = 'prev', 'next', 'key', '__weakref__' - -class OrderedDict(dict): - 'Dictionary that remembers insertion order' - # An inherited dict maps keys to values. - # The inherited dict provides __getitem__, __len__, __contains__, and get. - # The remaining methods are order-aware. - # Big-O running times for all methods are the same as regular dictionaries. - - # The internal self.__map dict maps keys to links in a doubly linked list. - # The circular doubly linked list starts and ends with a sentinel element. - # The sentinel element never gets deleted (this simplifies the algorithm). - # The sentinel is in self.__hardroot with a weakref proxy in self.__root. - # The prev links are weakref proxies (to prevent circular references). - # Individual links are kept alive by the hard reference in self.__map. - # Those hard references disappear when a key is deleted from an OrderedDict. - - def __init__(self, other=(), /, **kwds): - '''Initialize an ordered dictionary. The signature is the same as - regular dictionaries. Keyword argument order is preserved. - ''' - try: - self.__root - except AttributeError: - self.__hardroot = _Link() - self.__root = root = _proxy(self.__hardroot) - root.prev = root.next = root - self.__map = {} - self.__update(other, **kwds) - - def __setitem__(self, key, value, - dict_setitem=dict.__setitem__, proxy=_proxy, Link=_Link): - 'od.__setitem__(i, y) <==> od[i]=y' - # Setting a new item creates a new link at the end of the linked list, - # and the inherited dictionary is updated with the new key/value pair. - if key not in self: - self.__map[key] = link = Link() - root = self.__root - last = root.prev - link.prev, link.next, link.key = last, root, key - last.next = link - root.prev = proxy(link) - dict_setitem(self, key, value) - - def __delitem__(self, key, dict_delitem=dict.__delitem__): - 'od.__delitem__(y) <==> del od[y]' - # Deleting an existing item uses self.__map to find the link which gets - # removed by updating the links in the predecessor and successor nodes. - dict_delitem(self, key) - link = self.__map.pop(key) - link_prev = link.prev - link_next = link.next - link_prev.next = link_next - link_next.prev = link_prev - link.prev = None - link.next = None - - def __iter__(self): - 'od.__iter__() <==> iter(od)' - # Traverse the linked list in order. - root = self.__root - curr = root.next - while curr is not root: - yield curr.key - curr = curr.next - - def __reversed__(self): - 'od.__reversed__() <==> reversed(od)' - # Traverse the linked list in reverse order. - root = self.__root - curr = root.prev - while curr is not root: - yield curr.key - curr = curr.prev - - def clear(self): - 'od.clear() -> None. Remove all items from od.' - root = self.__root - root.prev = root.next = root - self.__map.clear() - dict.clear(self) - - def popitem(self, last=True): - '''Remove and return a (key, value) pair from the dictionary. - - Pairs are returned in LIFO order if last is true or FIFO order if false. - ''' - if not self: - raise KeyError('dictionary is empty') - root = self.__root - if last: - link = root.prev - link_prev = link.prev - link_prev.next = root - root.prev = link_prev - else: - link = root.next - link_next = link.next - root.next = link_next - link_next.prev = root - key = link.key - del self.__map[key] - value = dict.pop(self, key) - return key, value - - def move_to_end(self, key, last=True): - '''Move an existing element to the end (or beginning if last is false). - - Raise KeyError if the element does not exist. - ''' - link = self.__map[key] - link_prev = link.prev - link_next = link.next - soft_link = link_next.prev - link_prev.next = link_next - link_next.prev = link_prev - root = self.__root - if last: - last = root.prev - link.prev = last - link.next = root - root.prev = soft_link - last.next = link - else: - first = root.next - link.prev = root - link.next = first - first.prev = soft_link - root.next = link - - def __sizeof__(self): - sizeof = _sys.getsizeof - n = len(self) + 1 # number of links including root - size = sizeof(self.__dict__) # instance dictionary - size += sizeof(self.__map) * 2 # internal dict and inherited dict - size += sizeof(self.__hardroot) * n # link objects - size += sizeof(self.__root) * n # proxy objects - return size - - update = __update = _collections_abc.MutableMapping.update - - def keys(self): - "D.keys() -> a set-like object providing a view on D's keys" - return _OrderedDictKeysView(self) - - def items(self): - "D.items() -> a set-like object providing a view on D's items" - return _OrderedDictItemsView(self) - - def values(self): - "D.values() -> an object providing a view on D's values" - return _OrderedDictValuesView(self) - - __ne__ = _collections_abc.MutableMapping.__ne__ - - __marker = object() - - def pop(self, key, default=__marker): - '''od.pop(k[,d]) -> v, remove specified key and return the corresponding - value. If key is not found, d is returned if given, otherwise KeyError - is raised. - - ''' - if key in self: - result = self[key] - del self[key] - return result - if default is self.__marker: - raise KeyError(key) - return default - - def setdefault(self, key, default=None): - '''Insert key with a value of default if key is not in the dictionary. - - Return the value for key if key is in the dictionary, else default. - ''' - if key in self: - return self[key] - self[key] = default - return default - - @_recursive_repr() - def __repr__(self): - 'od.__repr__() <==> repr(od)' - if not self: - return '%s()' % (self.__class__.__name__,) - return '%s(%r)' % (self.__class__.__name__, list(self.items())) - - def __reduce__(self): - 'Return state information for pickling' - inst_dict = vars(self).copy() - for k in vars(OrderedDict()): - inst_dict.pop(k, None) - return self.__class__, (), inst_dict or None, None, iter(self.items()) - - def copy(self): - 'od.copy() -> a shallow copy of od' - return self.__class__(self) - - @classmethod - def fromkeys(cls, iterable, value=None): - '''Create a new ordered dictionary with keys from iterable and values set to value. - ''' - self = cls() - for key in iterable: - self[key] = value - return self - - def __eq__(self, other): - '''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive - while comparison to a regular mapping is order-insensitive. - - ''' - if isinstance(other, OrderedDict): - return dict.__eq__(self, other) and all(map(_eq, self, other)) - return dict.__eq__(self, other) - - -try: - from _collections import OrderedDict -except ImportError: - # Leave the pure Python version in place. - pass - - -################################################################################ -### namedtuple -################################################################################ - -try: - from _collections import _tuplegetter -except ImportError: - _tuplegetter = lambda index, doc: property(_itemgetter(index), doc=doc) - -def namedtuple(typename, field_names, *, rename=False, defaults=None, module=None): - """Returns a new subclass of tuple with named fields. - - >>> Point = namedtuple('Point', ['x', 'y']) - >>> Point.__doc__ # docstring for the new class - 'Point(x, y)' - >>> p = Point(11, y=22) # instantiate with positional args or keywords - >>> p[0] + p[1] # indexable like a plain tuple - 33 - >>> x, y = p # unpack like a regular tuple - >>> x, y - (11, 22) - >>> p.x + p.y # fields also accessible by name - 33 - >>> d = p._asdict() # convert to a dictionary - >>> d['x'] - 11 - >>> Point(**d) # convert from a dictionary - Point(x=11, y=22) - >>> p._replace(x=100) # _replace() is like str.replace() but targets named fields - Point(x=100, y=22) - - """ - - # Validate the field names. At the user's option, either generate an error - # message or automatically replace the field name with a valid name. - if isinstance(field_names, str): - field_names = field_names.replace(',', ' ').split() - field_names = list(map(str, field_names)) - typename = _sys.intern(str(typename)) - - if rename: - seen = set() - for index, name in enumerate(field_names): - if (not name.isidentifier() - or _iskeyword(name) - or name.startswith('_') - or name in seen): - field_names[index] = f'_{index}' - seen.add(name) - - for name in [typename] + field_names: - if type(name) is not str: - raise TypeError('Type names and field names must be strings') - if not name.isidentifier(): - raise ValueError('Type names and field names must be valid ' - f'identifiers: {name!r}') - if _iskeyword(name): - raise ValueError('Type names and field names cannot be a ' - f'keyword: {name!r}') - - seen = set() - for name in field_names: - if name.startswith('_') and not rename: - raise ValueError('Field names cannot start with an underscore: ' - f'{name!r}') - if name in seen: - raise ValueError(f'Encountered duplicate field name: {name!r}') - seen.add(name) - - field_defaults = {} - if defaults is not None: - defaults = tuple(defaults) - if len(defaults) > len(field_names): - raise TypeError('Got more default values than field names') - field_defaults = dict(reversed(list(zip(reversed(field_names), - reversed(defaults))))) - - # Variables used in the methods and docstrings - field_names = tuple(map(_sys.intern, field_names)) - num_fields = len(field_names) - arg_list = repr(field_names).replace("'", "")[1:-1] - repr_fmt = '(' + ', '.join(f'{name}=%r' for name in field_names) + ')' - tuple_new = tuple.__new__ - _dict, _tuple, _len, _map, _zip = dict, tuple, len, map, zip - - # Create all the named tuple methods to be added to the class namespace - - s = f'def __new__(_cls, {arg_list}): return _tuple_new(_cls, ({arg_list}))' - namespace = {'_tuple_new': tuple_new, '__name__': f'namedtuple_{typename}'} - # Note: exec() has the side-effect of interning the field names - exec(s, namespace) - __new__ = namespace['__new__'] - __new__.__doc__ = f'Create new instance of {typename}({arg_list})' - if defaults is not None: - __new__.__defaults__ = defaults - - @classmethod - def _make(cls, iterable): - result = tuple_new(cls, iterable) - if _len(result) != num_fields: - raise TypeError(f'Expected {num_fields} arguments, got {len(result)}') - return result - - _make.__func__.__doc__ = (f'Make a new {typename} object from a sequence ' - 'or iterable') - - def _replace(self, /, **kwds): - result = self._make(_map(kwds.pop, field_names, self)) - if kwds: - raise ValueError(f'Got unexpected field names: {list(kwds)!r}') - return result - - _replace.__doc__ = (f'Return a new {typename} object replacing specified ' - 'fields with new values') - - def __repr__(self): - 'Return a nicely formatted representation string' - return self.__class__.__name__ + repr_fmt % self - - def _asdict(self): - 'Return a new dict which maps field names to their values.' - return _dict(_zip(self._fields, self)) - - def __getnewargs__(self): - 'Return self as a plain tuple. Used by copy and pickle.' - return _tuple(self) - - # Modify function metadata to help with introspection and debugging - for method in (__new__, _make.__func__, _replace, - __repr__, _asdict, __getnewargs__): - method.__qualname__ = f'{typename}.{method.__name__}' - - # Build-up the class namespace dictionary - # and use type() to build the result class - class_namespace = { - '__doc__': f'{typename}({arg_list})', - '__slots__': (), - '_fields': field_names, - '_field_defaults': field_defaults, - # alternate spelling for backward compatibility - '_fields_defaults': field_defaults, - '__new__': __new__, - '_make': _make, - '_replace': _replace, - '__repr__': __repr__, - '_asdict': _asdict, - '__getnewargs__': __getnewargs__, - } - for index, name in enumerate(field_names): - doc = _sys.intern(f'Alias for field number {index}') - class_namespace[name] = _tuplegetter(index, doc) - - result = type(typename, (tuple,), class_namespace) - - # For pickling to work, the __module__ variable needs to be set to the frame - # where the named tuple is created. Bypass this step in environments where - # sys._getframe is not defined (Jython for example) or sys._getframe is not - # defined for arguments greater than 0 (IronPython), or where the user has - # specified a particular module. - if module is None: - try: - module = _sys._getframe(1).f_globals.get('__name__', '__main__') - except (AttributeError, ValueError): - pass - if module is not None: - result.__module__ = module - - return result - - -######################################################################## -### Counter -######################################################################## - -def _count_elements(mapping, iterable): - 'Tally elements from the iterable.' - mapping_get = mapping.get - for elem in iterable: - mapping[elem] = mapping_get(elem, 0) + 1 - -try: # Load C helper function if available - from _collections import _count_elements -except ImportError: - pass - -class Counter(dict): - '''Dict subclass for counting hashable items. Sometimes called a bag - or multiset. Elements are stored as dictionary keys and their counts - are stored as dictionary values. - - >>> c = Counter('abcdeabcdabcaba') # count elements from a string - - >>> c.most_common(3) # three most common elements - [('a', 5), ('b', 4), ('c', 3)] - >>> sorted(c) # list all unique elements - ['a', 'b', 'c', 'd', 'e'] - >>> ''.join(sorted(c.elements())) # list elements with repetitions - 'aaaaabbbbcccdde' - >>> sum(c.values()) # total of all counts - 15 - - >>> c['a'] # count of letter 'a' - 5 - >>> for elem in 'shazam': # update counts from an iterable - ... c[elem] += 1 # by adding 1 to each element's count - >>> c['a'] # now there are seven 'a' - 7 - >>> del c['b'] # remove all 'b' - >>> c['b'] # now there are zero 'b' - 0 - - >>> d = Counter('simsalabim') # make another counter - >>> c.update(d) # add in the second counter - >>> c['a'] # now there are nine 'a' - 9 - - >>> c.clear() # empty the counter - >>> c - Counter() - - Note: If a count is set to zero or reduced to zero, it will remain - in the counter until the entry is deleted or the counter is cleared: - - >>> c = Counter('aaabbc') - >>> c['b'] -= 2 # reduce the count of 'b' by two - >>> c.most_common() # 'b' is still in, but its count is zero - [('a', 3), ('c', 1), ('b', 0)] - - ''' - # References: - # http://en.wikipedia.org/wiki/Multiset - # http://www.gnu.org/software/smalltalk/manual-base/html_node/Bag.html - # http://www.demo2s.com/Tutorial/Cpp/0380__set-multiset/Catalog0380__set-multiset.htm - # http://code.activestate.com/recipes/259174/ - # Knuth, TAOCP Vol. II section 4.6.3 - - def __init__(self, iterable=None, /, **kwds): - '''Create a new, empty Counter object. And if given, count elements - from an input iterable. Or, initialize the count from another mapping - of elements to their counts. - - >>> c = Counter() # a new, empty counter - >>> c = Counter('gallahad') # a new counter from an iterable - >>> c = Counter({'a': 4, 'b': 2}) # a new counter from a mapping - >>> c = Counter(a=4, b=2) # a new counter from keyword args - - ''' - super(Counter, self).__init__() - self.update(iterable, **kwds) - - def __missing__(self, key): - 'The count of elements not in the Counter is zero.' - # Needed so that self[missing_item] does not raise KeyError - return 0 - - def most_common(self, n=None): - '''List the n most common elements and their counts from the most - common to the least. If n is None, then list all element counts. - - >>> Counter('abracadabra').most_common(3) - [('a', 5), ('b', 2), ('r', 2)] - - ''' - # Emulate Bag.sortedByCount from Smalltalk - if n is None: - return sorted(self.items(), key=_itemgetter(1), reverse=True) - return _heapq.nlargest(n, self.items(), key=_itemgetter(1)) - - def elements(self): - '''Iterator over elements repeating each as many times as its count. - - >>> c = Counter('ABCABC') - >>> sorted(c.elements()) - ['A', 'A', 'B', 'B', 'C', 'C'] - - # Knuth's example for prime factors of 1836: 2**2 * 3**3 * 17**1 - >>> prime_factors = Counter({2: 2, 3: 3, 17: 1}) - >>> product = 1 - >>> for factor in prime_factors.elements(): # loop over factors - ... product *= factor # and multiply them - >>> product - 1836 - - Note, if an element's count has been set to zero or is a negative - number, elements() will ignore it. - - ''' - # Emulate Bag.do from Smalltalk and Multiset.begin from C++. - return _chain.from_iterable(_starmap(_repeat, self.items())) - - # Override dict methods where necessary - - @classmethod - def fromkeys(cls, iterable, v=None): - # There is no equivalent method for counters because the semantics - # would be ambiguous in cases such as Counter.fromkeys('aaabbc', v=2). - # Initializing counters to zero values isn't necessary because zero - # is already the default value for counter lookups. Initializing - # to one is easily accomplished with Counter(set(iterable)). For - # more exotic cases, create a dictionary first using a dictionary - # comprehension or dict.fromkeys(). - raise NotImplementedError( - 'Counter.fromkeys() is undefined. Use Counter(iterable) instead.') - - def update(self, iterable=None, /, **kwds): - '''Like dict.update() but add counts instead of replacing them. - - Source can be an iterable, a dictionary, or another Counter instance. - - >>> c = Counter('which') - >>> c.update('witch') # add elements from another iterable - >>> d = Counter('watch') - >>> c.update(d) # add elements from another counter - >>> c['h'] # four 'h' in which, witch, and watch - 4 - - ''' - # The regular dict.update() operation makes no sense here because the - # replace behavior results in the some of original untouched counts - # being mixed-in with all of the other counts for a mismash that - # doesn't have a straight-forward interpretation in most counting - # contexts. Instead, we implement straight-addition. Both the inputs - # and outputs are allowed to contain zero and negative counts. - - if iterable is not None: - if isinstance(iterable, _collections_abc.Mapping): - if self: - self_get = self.get - for elem, count in iterable.items(): - self[elem] = count + self_get(elem, 0) - else: - super(Counter, self).update(iterable) # fast path when counter is empty - else: - _count_elements(self, iterable) - if kwds: - self.update(kwds) - - def subtract(self, iterable=None, /, **kwds): - '''Like dict.update() but subtracts counts instead of replacing them. - Counts can be reduced below zero. Both the inputs and outputs are - allowed to contain zero and negative counts. - - Source can be an iterable, a dictionary, or another Counter instance. - - >>> c = Counter('which') - >>> c.subtract('witch') # subtract elements from another iterable - >>> c.subtract(Counter('watch')) # subtract elements from another counter - >>> c['h'] # 2 in which, minus 1 in witch, minus 1 in watch - 0 - >>> c['w'] # 1 in which, minus 1 in witch, minus 1 in watch - -1 - - ''' - if iterable is not None: - self_get = self.get - if isinstance(iterable, _collections_abc.Mapping): - for elem, count in iterable.items(): - self[elem] = self_get(elem, 0) - count - else: - for elem in iterable: - self[elem] = self_get(elem, 0) - 1 - if kwds: - self.subtract(kwds) - - def copy(self): - 'Return a shallow copy.' - return self.__class__(self) - - def __reduce__(self): - return self.__class__, (dict(self),) - - def __delitem__(self, elem): - 'Like dict.__delitem__() but does not raise KeyError for missing values.' - if elem in self: - super().__delitem__(elem) - - def __repr__(self): - if not self: - return '%s()' % self.__class__.__name__ - try: - items = ', '.join(map('%r: %r'.__mod__, self.most_common())) - return '%s({%s})' % (self.__class__.__name__, items) - except TypeError: - # handle case where values are not orderable - return '{0}({1!r})'.format(self.__class__.__name__, dict(self)) - - # Multiset-style mathematical operations discussed in: - # Knuth TAOCP Volume II section 4.6.3 exercise 19 - # and at http://en.wikipedia.org/wiki/Multiset - # - # Outputs guaranteed to only include positive counts. - # - # To strip negative and zero counts, add-in an empty counter: - # c += Counter() - # - # Rich comparison operators for multiset subset and superset tests - # are deliberately omitted due to semantic conflicts with the - # existing inherited dict equality method. Subset and superset - # semantics ignore zero counts and require that p≤q ∧ p≥q → p=q; - # however, that would not be the case for p=Counter(a=1, b=0) - # and q=Counter(a=1) where the dictionaries are not equal. - - def __add__(self, other): - '''Add counts from two counters. - - >>> Counter('abbb') + Counter('bcc') - Counter({'b': 4, 'c': 2, 'a': 1}) - - ''' - if not isinstance(other, Counter): - return NotImplemented - result = Counter() - for elem, count in self.items(): - newcount = count + other[elem] - if newcount > 0: - result[elem] = newcount - for elem, count in other.items(): - if elem not in self and count > 0: - result[elem] = count - return result - - def __sub__(self, other): - ''' Subtract count, but keep only results with positive counts. - - >>> Counter('abbbc') - Counter('bccd') - Counter({'b': 2, 'a': 1}) - - ''' - if not isinstance(other, Counter): - return NotImplemented - result = Counter() - for elem, count in self.items(): - newcount = count - other[elem] - if newcount > 0: - result[elem] = newcount - for elem, count in other.items(): - if elem not in self and count < 0: - result[elem] = 0 - count - return result - - def __or__(self, other): - '''Union is the maximum of value in either of the input counters. - - >>> Counter('abbb') | Counter('bcc') - Counter({'b': 3, 'c': 2, 'a': 1}) - - ''' - if not isinstance(other, Counter): - return NotImplemented - result = Counter() - for elem, count in self.items(): - other_count = other[elem] - newcount = other_count if count < other_count else count - if newcount > 0: - result[elem] = newcount - for elem, count in other.items(): - if elem not in self and count > 0: - result[elem] = count - return result - - def __and__(self, other): - ''' Intersection is the minimum of corresponding counts. - - >>> Counter('abbb') & Counter('bcc') - Counter({'b': 1}) - - ''' - if not isinstance(other, Counter): - return NotImplemented - result = Counter() - for elem, count in self.items(): - other_count = other[elem] - newcount = count if count < other_count else other_count - if newcount > 0: - result[elem] = newcount - return result - - def __pos__(self): - 'Adds an empty counter, effectively stripping negative and zero counts' - result = Counter() - for elem, count in self.items(): - if count > 0: - result[elem] = count - return result - - def __neg__(self): - '''Subtracts from an empty counter. Strips positive and zero counts, - and flips the sign on negative counts. - - ''' - result = Counter() - for elem, count in self.items(): - if count < 0: - result[elem] = 0 - count - return result - - def _keep_positive(self): - '''Internal method to strip elements with a negative or zero count''' - nonpositive = [elem for elem, count in self.items() if not count > 0] - for elem in nonpositive: - del self[elem] - return self - - def __iadd__(self, other): - '''Inplace add from another counter, keeping only positive counts. - - >>> c = Counter('abbb') - >>> c += Counter('bcc') - >>> c - Counter({'b': 4, 'c': 2, 'a': 1}) - - ''' - for elem, count in other.items(): - self[elem] += count - return self._keep_positive() - - def __isub__(self, other): - '''Inplace subtract counter, but keep only results with positive counts. - - >>> c = Counter('abbbc') - >>> c -= Counter('bccd') - >>> c - Counter({'b': 2, 'a': 1}) - - ''' - for elem, count in other.items(): - self[elem] -= count - return self._keep_positive() - - def __ior__(self, other): - '''Inplace union is the maximum of value from either counter. - - >>> c = Counter('abbb') - >>> c |= Counter('bcc') - >>> c - Counter({'b': 3, 'c': 2, 'a': 1}) - - ''' - for elem, other_count in other.items(): - count = self[elem] - if other_count > count: - self[elem] = other_count - return self._keep_positive() - - def __iand__(self, other): - '''Inplace intersection is the minimum of corresponding counts. - - >>> c = Counter('abbb') - >>> c &= Counter('bcc') - >>> c - Counter({'b': 1}) - - ''' - for elem, count in self.items(): - other_count = other[elem] - if other_count < count: - self[elem] = other_count - return self._keep_positive() - - -######################################################################## -### ChainMap -######################################################################## - -class ChainMap(_collections_abc.MutableMapping): - ''' A ChainMap groups multiple dicts (or other mappings) together - to create a single, updateable view. - - The underlying mappings are stored in a list. That list is public and can - be accessed or updated using the *maps* attribute. There is no other - state. - - Lookups search the underlying mappings successively until a key is found. - In contrast, writes, updates, and deletions only operate on the first - mapping. - - ''' - - def __init__(self, *maps): - '''Initialize a ChainMap by setting *maps* to the given mappings. - If no mappings are provided, a single empty dictionary is used. - - ''' - self.maps = list(maps) or [{}] # always at least one map - - def __missing__(self, key): - raise KeyError(key) - - def __getitem__(self, key): - for mapping in self.maps: - try: - return mapping[key] # can't use 'key in mapping' with defaultdict - except KeyError: - pass - return self.__missing__(key) # support subclasses that define __missing__ - - def get(self, key, default=None): - return self[key] if key in self else default - - def __len__(self): - return len(set().union(*self.maps)) # reuses stored hash values if possible - - def __iter__(self): - d = {} - for mapping in reversed(self.maps): - d.update(mapping) # reuses stored hash values if possible - return iter(d) - - def __contains__(self, key): - return any(key in m for m in self.maps) - - def __bool__(self): - return any(self.maps) - - @_recursive_repr() - def __repr__(self): - return f'{self.__class__.__name__}({", ".join(map(repr, self.maps))})' - - @classmethod - def fromkeys(cls, iterable, *args): - 'Create a ChainMap with a single dict created from the iterable.' - return cls(dict.fromkeys(iterable, *args)) - - def copy(self): - 'New ChainMap or subclass with a new copy of maps[0] and refs to maps[1:]' - return self.__class__(self.maps[0].copy(), *self.maps[1:]) - - __copy__ = copy - - def new_child(self, m=None): # like Django's Context.push() - '''New ChainMap with a new map followed by all previous maps. - If no map is provided, an empty dict is used. - ''' - if m is None: - m = {} - return self.__class__(m, *self.maps) - - @property - def parents(self): # like Django's Context.pop() - 'New ChainMap from maps[1:].' - return self.__class__(*self.maps[1:]) - - def __setitem__(self, key, value): - self.maps[0][key] = value - - def __delitem__(self, key): - try: - del self.maps[0][key] - except KeyError: - raise KeyError('Key not found in the first mapping: {!r}'.format(key)) - - def popitem(self): - 'Remove and return an item pair from maps[0]. Raise KeyError is maps[0] is empty.' - try: - return self.maps[0].popitem() - except KeyError: - raise KeyError('No keys found in the first mapping.') - - def pop(self, key, *args): - 'Remove *key* from maps[0] and return its value. Raise KeyError if *key* not in maps[0].' - try: - return self.maps[0].pop(key, *args) - except KeyError: - raise KeyError('Key not found in the first mapping: {!r}'.format(key)) - - def clear(self): - 'Clear maps[0], leaving maps[1:] intact.' - self.maps[0].clear() - - -################################################################################ -### UserDict -################################################################################ - -class UserDict(_collections_abc.MutableMapping): - - # Start by filling-out the abstract methods - def __init__(*args, **kwargs): - if not args: - raise TypeError("descriptor '__init__' of 'UserDict' object " - "needs an argument") - self, *args = args - if len(args) > 1: - raise TypeError('expected at most 1 arguments, got %d' % len(args)) - if args: - dict = args[0] - elif 'dict' in kwargs: - dict = kwargs.pop('dict') - import warnings - warnings.warn("Passing 'dict' as keyword argument is deprecated", - DeprecationWarning, stacklevel=2) - else: - dict = None - self.data = {} - if dict is not None: - self.update(dict) - if kwargs: - self.update(kwargs) - __init__.__text_signature__ = '($self, dict=None, /, **kwargs)' - - def __len__(self): return len(self.data) - def __getitem__(self, key): - if key in self.data: - return self.data[key] - if hasattr(self.__class__, "__missing__"): - return self.__class__.__missing__(self, key) - raise KeyError(key) - def __setitem__(self, key, item): self.data[key] = item - def __delitem__(self, key): del self.data[key] - def __iter__(self): - return iter(self.data) - - # Modify __contains__ to work correctly when __missing__ is present - def __contains__(self, key): - return key in self.data - - # Now, add the methods in dicts but not in MutableMapping - def __repr__(self): return repr(self.data) - def __copy__(self): - inst = self.__class__.__new__(self.__class__) - inst.__dict__.update(self.__dict__) - # Create a copy and avoid triggering descriptors - inst.__dict__["data"] = self.__dict__["data"].copy() - return inst - - def copy(self): - if self.__class__ is UserDict: - return UserDict(self.data.copy()) - import copy - data = self.data - try: - self.data = {} - c = copy.copy(self) - finally: - self.data = data - c.update(self) - return c - - @classmethod - def fromkeys(cls, iterable, value=None): - d = cls() - for key in iterable: - d[key] = value - return d - - - -################################################################################ -### UserList -################################################################################ - -class UserList(_collections_abc.MutableSequence): - """A more or less complete user-defined wrapper around list objects.""" - def __init__(self, initlist=None): - self.data = [] - if initlist is not None: - # XXX should this accept an arbitrary sequence? - if type(initlist) == type(self.data): - self.data[:] = initlist - elif isinstance(initlist, UserList): - self.data[:] = initlist.data[:] - else: - self.data = list(initlist) - def __repr__(self): return repr(self.data) - def __lt__(self, other): return self.data < self.__cast(other) - def __le__(self, other): return self.data <= self.__cast(other) - def __eq__(self, other): return self.data == self.__cast(other) - def __gt__(self, other): return self.data > self.__cast(other) - def __ge__(self, other): return self.data >= self.__cast(other) - def __cast(self, other): - return other.data if isinstance(other, UserList) else other - def __contains__(self, item): return item in self.data - def __len__(self): return len(self.data) - def __getitem__(self, i): - if isinstance(i, slice): - return self.__class__(self.data[i]) - else: - return self.data[i] - def __setitem__(self, i, item): self.data[i] = item - def __delitem__(self, i): del self.data[i] - def __add__(self, other): - if isinstance(other, UserList): - return self.__class__(self.data + other.data) - elif isinstance(other, type(self.data)): - return self.__class__(self.data + other) - return self.__class__(self.data + list(other)) - def __radd__(self, other): - if isinstance(other, UserList): - return self.__class__(other.data + self.data) - elif isinstance(other, type(self.data)): - return self.__class__(other + self.data) - return self.__class__(list(other) + self.data) - def __iadd__(self, other): - if isinstance(other, UserList): - self.data += other.data - elif isinstance(other, type(self.data)): - self.data += other - else: - self.data += list(other) - return self - def __mul__(self, n): - return self.__class__(self.data*n) - __rmul__ = __mul__ - def __imul__(self, n): - self.data *= n - return self - def __copy__(self): - inst = self.__class__.__new__(self.__class__) - inst.__dict__.update(self.__dict__) - # Create a copy and avoid triggering descriptors - inst.__dict__["data"] = self.__dict__["data"][:] - return inst - def append(self, item): self.data.append(item) - def insert(self, i, item): self.data.insert(i, item) - def pop(self, i=-1): return self.data.pop(i) - def remove(self, item): self.data.remove(item) - def clear(self): self.data.clear() - def copy(self): return self.__class__(self) - def count(self, item): return self.data.count(item) - def index(self, item, *args): return self.data.index(item, *args) - def reverse(self): self.data.reverse() - def sort(self, /, *args, **kwds): self.data.sort(*args, **kwds) - def extend(self, other): - if isinstance(other, UserList): - self.data.extend(other.data) - else: - self.data.extend(other) - - - -################################################################################ -### UserString -################################################################################ - -class UserString(_collections_abc.Sequence): - def __init__(self, seq): - if isinstance(seq, str): - self.data = seq - elif isinstance(seq, UserString): - self.data = seq.data[:] - else: - self.data = str(seq) - def __str__(self): return str(self.data) - def __repr__(self): return repr(self.data) - def __int__(self): return int(self.data) - def __float__(self): return float(self.data) - def __complex__(self): return complex(self.data) - def __hash__(self): return hash(self.data) - def __getnewargs__(self): - return (self.data[:],) - - def __eq__(self, string): - if isinstance(string, UserString): - return self.data == string.data - return self.data == string - def __lt__(self, string): - if isinstance(string, UserString): - return self.data < string.data - return self.data < string - def __le__(self, string): - if isinstance(string, UserString): - return self.data <= string.data - return self.data <= string - def __gt__(self, string): - if isinstance(string, UserString): - return self.data > string.data - return self.data > string - def __ge__(self, string): - if isinstance(string, UserString): - return self.data >= string.data - return self.data >= string - - def __contains__(self, char): - if isinstance(char, UserString): - char = char.data - return char in self.data - - def __len__(self): return len(self.data) - def __getitem__(self, index): return self.__class__(self.data[index]) - def __add__(self, other): - if isinstance(other, UserString): - return self.__class__(self.data + other.data) - elif isinstance(other, str): - return self.__class__(self.data + other) - return self.__class__(self.data + str(other)) - def __radd__(self, other): - if isinstance(other, str): - return self.__class__(other + self.data) - return self.__class__(str(other) + self.data) - def __mul__(self, n): - return self.__class__(self.data*n) - __rmul__ = __mul__ - def __mod__(self, args): - return self.__class__(self.data % args) - def __rmod__(self, template): - return self.__class__(str(template) % self) - # the following methods are defined in alphabetical order: - def capitalize(self): return self.__class__(self.data.capitalize()) - def casefold(self): - return self.__class__(self.data.casefold()) - def center(self, width, *args): - return self.__class__(self.data.center(width, *args)) - def count(self, sub, start=0, end=_sys.maxsize): - if isinstance(sub, UserString): - sub = sub.data - return self.data.count(sub, start, end) - def encode(self, encoding='utf-8', errors='strict'): - encoding = 'utf-8' if encoding is None else encoding - errors = 'strict' if errors is None else errors - return self.data.encode(encoding, errors) - def endswith(self, suffix, start=0, end=_sys.maxsize): - return self.data.endswith(suffix, start, end) - def expandtabs(self, tabsize=8): - return self.__class__(self.data.expandtabs(tabsize)) - def find(self, sub, start=0, end=_sys.maxsize): - if isinstance(sub, UserString): - sub = sub.data - return self.data.find(sub, start, end) - def format(self, /, *args, **kwds): - return self.data.format(*args, **kwds) - def format_map(self, mapping): - return self.data.format_map(mapping) - def index(self, sub, start=0, end=_sys.maxsize): - return self.data.index(sub, start, end) - def isalpha(self): return self.data.isalpha() - def isalnum(self): return self.data.isalnum() - def isascii(self): return self.data.isascii() - def isdecimal(self): return self.data.isdecimal() - def isdigit(self): return self.data.isdigit() - def isidentifier(self): return self.data.isidentifier() - def islower(self): return self.data.islower() - def isnumeric(self): return self.data.isnumeric() - def isprintable(self): return self.data.isprintable() - def isspace(self): return self.data.isspace() - def istitle(self): return self.data.istitle() - def isupper(self): return self.data.isupper() - def join(self, seq): return self.data.join(seq) - def ljust(self, width, *args): - return self.__class__(self.data.ljust(width, *args)) - def lower(self): return self.__class__(self.data.lower()) - def lstrip(self, chars=None): return self.__class__(self.data.lstrip(chars)) - maketrans = str.maketrans - def partition(self, sep): - return self.data.partition(sep) - def replace(self, old, new, maxsplit=-1): - if isinstance(old, UserString): - old = old.data - if isinstance(new, UserString): - new = new.data - return self.__class__(self.data.replace(old, new, maxsplit)) - def rfind(self, sub, start=0, end=_sys.maxsize): - if isinstance(sub, UserString): - sub = sub.data - return self.data.rfind(sub, start, end) - def rindex(self, sub, start=0, end=_sys.maxsize): - return self.data.rindex(sub, start, end) - def rjust(self, width, *args): - return self.__class__(self.data.rjust(width, *args)) - def rpartition(self, sep): - return self.data.rpartition(sep) - def rstrip(self, chars=None): - return self.__class__(self.data.rstrip(chars)) - def split(self, sep=None, maxsplit=-1): - return self.data.split(sep, maxsplit) - def rsplit(self, sep=None, maxsplit=-1): - return self.data.rsplit(sep, maxsplit) - def splitlines(self, keepends=False): return self.data.splitlines(keepends) - def startswith(self, prefix, start=0, end=_sys.maxsize): - return self.data.startswith(prefix, start, end) - def strip(self, chars=None): return self.__class__(self.data.strip(chars)) - def swapcase(self): return self.__class__(self.data.swapcase()) - def title(self): return self.__class__(self.data.title()) - def translate(self, *args): - return self.__class__(self.data.translate(*args)) - def upper(self): return self.__class__(self.data.upper()) - def zfill(self, width): return self.__class__(self.data.zfill(width)) diff --git a/dist/lib/collections/__pycache__/__init__.cpython-38.opt-1.pyc b/dist/lib/collections/__pycache__/__init__.cpython-38.opt-1.pyc deleted file mode 100644 index d1be6c9..0000000 Binary files a/dist/lib/collections/__pycache__/__init__.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/collections/__pycache__/abc.cpython-38.opt-1.pyc b/dist/lib/collections/__pycache__/abc.cpython-38.opt-1.pyc deleted file mode 100644 index 65996c4..0000000 Binary files a/dist/lib/collections/__pycache__/abc.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/collections/abc.py b/dist/lib/collections/abc.py deleted file mode 100644 index 891600d..0000000 --- a/dist/lib/collections/abc.py +++ /dev/null @@ -1,2 +0,0 @@ -from _collections_abc import * -from _collections_abc import __all__ diff --git a/dist/lib/colorsys.py b/dist/lib/colorsys.py deleted file mode 100644 index b93e384..0000000 --- a/dist/lib/colorsys.py +++ /dev/null @@ -1,164 +0,0 @@ -"""Conversion functions between RGB and other color systems. - -This modules provides two functions for each color system ABC: - - rgb_to_abc(r, g, b) --> a, b, c - abc_to_rgb(a, b, c) --> r, g, b - -All inputs and outputs are triples of floats in the range [0.0...1.0] -(with the exception of I and Q, which covers a slightly larger range). -Inputs outside the valid range may cause exceptions or invalid outputs. - -Supported color systems: -RGB: Red, Green, Blue components -YIQ: Luminance, Chrominance (used by composite video signals) -HLS: Hue, Luminance, Saturation -HSV: Hue, Saturation, Value -""" - -# References: -# http://en.wikipedia.org/wiki/YIQ -# http://en.wikipedia.org/wiki/HLS_color_space -# http://en.wikipedia.org/wiki/HSV_color_space - -__all__ = ["rgb_to_yiq","yiq_to_rgb","rgb_to_hls","hls_to_rgb", - "rgb_to_hsv","hsv_to_rgb"] - -# Some floating point constants - -ONE_THIRD = 1.0/3.0 -ONE_SIXTH = 1.0/6.0 -TWO_THIRD = 2.0/3.0 - -# YIQ: used by composite video signals (linear combinations of RGB) -# Y: perceived grey level (0.0 == black, 1.0 == white) -# I, Q: color components -# -# There are a great many versions of the constants used in these formulae. -# The ones in this library uses constants from the FCC version of NTSC. - -def rgb_to_yiq(r, g, b): - y = 0.30*r + 0.59*g + 0.11*b - i = 0.74*(r-y) - 0.27*(b-y) - q = 0.48*(r-y) + 0.41*(b-y) - return (y, i, q) - -def yiq_to_rgb(y, i, q): - # r = y + (0.27*q + 0.41*i) / (0.74*0.41 + 0.27*0.48) - # b = y + (0.74*q - 0.48*i) / (0.74*0.41 + 0.27*0.48) - # g = y - (0.30*(r-y) + 0.11*(b-y)) / 0.59 - - r = y + 0.9468822170900693*i + 0.6235565819861433*q - g = y - 0.27478764629897834*i - 0.6356910791873801*q - b = y - 1.1085450346420322*i + 1.7090069284064666*q - - if r < 0.0: - r = 0.0 - if g < 0.0: - g = 0.0 - if b < 0.0: - b = 0.0 - if r > 1.0: - r = 1.0 - if g > 1.0: - g = 1.0 - if b > 1.0: - b = 1.0 - return (r, g, b) - - -# HLS: Hue, Luminance, Saturation -# H: position in the spectrum -# L: color lightness -# S: color saturation - -def rgb_to_hls(r, g, b): - maxc = max(r, g, b) - minc = min(r, g, b) - # XXX Can optimize (maxc+minc) and (maxc-minc) - l = (minc+maxc)/2.0 - if minc == maxc: - return 0.0, l, 0.0 - if l <= 0.5: - s = (maxc-minc) / (maxc+minc) - else: - s = (maxc-minc) / (2.0-maxc-minc) - rc = (maxc-r) / (maxc-minc) - gc = (maxc-g) / (maxc-minc) - bc = (maxc-b) / (maxc-minc) - if r == maxc: - h = bc-gc - elif g == maxc: - h = 2.0+rc-bc - else: - h = 4.0+gc-rc - h = (h/6.0) % 1.0 - return h, l, s - -def hls_to_rgb(h, l, s): - if s == 0.0: - return l, l, l - if l <= 0.5: - m2 = l * (1.0+s) - else: - m2 = l+s-(l*s) - m1 = 2.0*l - m2 - return (_v(m1, m2, h+ONE_THIRD), _v(m1, m2, h), _v(m1, m2, h-ONE_THIRD)) - -def _v(m1, m2, hue): - hue = hue % 1.0 - if hue < ONE_SIXTH: - return m1 + (m2-m1)*hue*6.0 - if hue < 0.5: - return m2 - if hue < TWO_THIRD: - return m1 + (m2-m1)*(TWO_THIRD-hue)*6.0 - return m1 - - -# HSV: Hue, Saturation, Value -# H: position in the spectrum -# S: color saturation ("purity") -# V: color brightness - -def rgb_to_hsv(r, g, b): - maxc = max(r, g, b) - minc = min(r, g, b) - v = maxc - if minc == maxc: - return 0.0, 0.0, v - s = (maxc-minc) / maxc - rc = (maxc-r) / (maxc-minc) - gc = (maxc-g) / (maxc-minc) - bc = (maxc-b) / (maxc-minc) - if r == maxc: - h = bc-gc - elif g == maxc: - h = 2.0+rc-bc - else: - h = 4.0+gc-rc - h = (h/6.0) % 1.0 - return h, s, v - -def hsv_to_rgb(h, s, v): - if s == 0.0: - return v, v, v - i = int(h*6.0) # XXX assume int() truncates! - f = (h*6.0) - i - p = v*(1.0 - s) - q = v*(1.0 - s*f) - t = v*(1.0 - s*(1.0-f)) - i = i%6 - if i == 0: - return v, t, p - if i == 1: - return q, v, p - if i == 2: - return p, v, t - if i == 3: - return p, q, v - if i == 4: - return t, p, v - if i == 5: - return v, p, q - # Cannot get here diff --git a/dist/lib/compileall.py b/dist/lib/compileall.py deleted file mode 100644 index bfac8ef..0000000 --- a/dist/lib/compileall.py +++ /dev/null @@ -1,333 +0,0 @@ -"""Module/script to byte-compile all .py files to .pyc files. - -When called as a script with arguments, this compiles the directories -given as arguments recursively; the -l option prevents it from -recursing into directories. - -Without arguments, if compiles all modules on sys.path, without -recursing into subdirectories. (Even though it should do so for -packages -- for now, you'll have to deal with packages separately.) - -See module py_compile for details of the actual byte-compilation. -""" -import os -import sys -import importlib.util -import py_compile -import struct - -from functools import partial - -__all__ = ["compile_dir","compile_file","compile_path"] - -def _walk_dir(dir, ddir=None, maxlevels=10, quiet=0): - if quiet < 2 and isinstance(dir, os.PathLike): - dir = os.fspath(dir) - if not quiet: - print('Listing {!r}...'.format(dir)) - try: - names = os.listdir(dir) - except OSError: - if quiet < 2: - print("Can't list {!r}".format(dir)) - names = [] - names.sort() - for name in names: - if name == '__pycache__': - continue - fullname = os.path.join(dir, name) - if ddir is not None: - dfile = os.path.join(ddir, name) - else: - dfile = None - if not os.path.isdir(fullname): - yield fullname, ddir - elif (maxlevels > 0 and name != os.curdir and name != os.pardir and - os.path.isdir(fullname) and not os.path.islink(fullname)): - yield from _walk_dir(fullname, ddir=dfile, - maxlevels=maxlevels - 1, quiet=quiet) - -def compile_dir(dir, maxlevels=10, ddir=None, force=False, rx=None, - quiet=0, legacy=False, optimize=-1, workers=1, - invalidation_mode=None): - """Byte-compile all modules in the given directory tree. - - Arguments (only dir is required): - - dir: the directory to byte-compile - maxlevels: maximum recursion level (default 10) - ddir: the directory that will be prepended to the path to the - file as it is compiled into each byte-code file. - force: if True, force compilation, even if timestamps are up-to-date - quiet: full output with False or 0, errors only with 1, - no output with 2 - legacy: if True, produce legacy pyc paths instead of PEP 3147 paths - optimize: optimization level or -1 for level of the interpreter - workers: maximum number of parallel workers - invalidation_mode: how the up-to-dateness of the pyc will be checked - """ - ProcessPoolExecutor = None - if workers < 0: - raise ValueError('workers must be greater or equal to 0') - if workers != 1: - try: - # Only import when needed, as low resource platforms may - # fail to import it - from concurrent.futures import ProcessPoolExecutor - except ImportError: - workers = 1 - files_and_ddirs = _walk_dir(dir, quiet=quiet, maxlevels=maxlevels, - ddir=ddir) - success = True - if workers != 1 and ProcessPoolExecutor is not None: - # If workers == 0, let ProcessPoolExecutor choose - workers = workers or None - with ProcessPoolExecutor(max_workers=workers) as executor: - results = executor.map( - partial(_compile_file_tuple, - force=force, rx=rx, quiet=quiet, - legacy=legacy, optimize=optimize, - invalidation_mode=invalidation_mode, - ), - files_and_ddirs) - success = min(results, default=True) - else: - for file, dfile in files_and_ddirs: - if not compile_file(file, dfile, force, rx, quiet, - legacy, optimize, invalidation_mode): - success = False - return success - -def _compile_file_tuple(file_and_dfile, **kwargs): - """Needs to be toplevel for ProcessPoolExecutor.""" - file, dfile = file_and_dfile - return compile_file(file, dfile, **kwargs) - -def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0, - legacy=False, optimize=-1, - invalidation_mode=None): - """Byte-compile one file. - - Arguments (only fullname is required): - - fullname: the file to byte-compile - ddir: if given, the directory name compiled in to the - byte-code file. - force: if True, force compilation, even if timestamps are up-to-date - quiet: full output with False or 0, errors only with 1, - no output with 2 - legacy: if True, produce legacy pyc paths instead of PEP 3147 paths - optimize: optimization level or -1 for level of the interpreter - invalidation_mode: how the up-to-dateness of the pyc will be checked - """ - success = True - if quiet < 2 and isinstance(fullname, os.PathLike): - fullname = os.fspath(fullname) - name = os.path.basename(fullname) - if ddir is not None: - dfile = os.path.join(ddir, name) - else: - dfile = None - if rx is not None: - mo = rx.search(fullname) - if mo: - return success - if os.path.isfile(fullname): - if legacy: - cfile = fullname + 'c' - else: - if optimize >= 0: - opt = optimize if optimize >= 1 else '' - cfile = importlib.util.cache_from_source( - fullname, optimization=opt) - else: - cfile = importlib.util.cache_from_source(fullname) - cache_dir = os.path.dirname(cfile) - head, tail = name[:-3], name[-3:] - if tail == '.py': - if not force: - try: - mtime = int(os.stat(fullname).st_mtime) - expect = struct.pack('<4sll', importlib.util.MAGIC_NUMBER, - 0, mtime) - with open(cfile, 'rb') as chandle: - actual = chandle.read(12) - if expect == actual: - return success - except OSError: - pass - if not quiet: - print('Compiling {!r}...'.format(fullname)) - try: - ok = py_compile.compile(fullname, cfile, dfile, True, - optimize=optimize, - invalidation_mode=invalidation_mode) - except py_compile.PyCompileError as err: - success = False - if quiet >= 2: - return success - elif quiet: - print('*** Error compiling {!r}...'.format(fullname)) - else: - print('*** ', end='') - # escape non-printable characters in msg - msg = err.msg.encode(sys.stdout.encoding, - errors='backslashreplace') - msg = msg.decode(sys.stdout.encoding) - print(msg) - except (SyntaxError, UnicodeError, OSError) as e: - success = False - if quiet >= 2: - return success - elif quiet: - print('*** Error compiling {!r}...'.format(fullname)) - else: - print('*** ', end='') - print(e.__class__.__name__ + ':', e) - else: - if ok == 0: - success = False - return success - -def compile_path(skip_curdir=1, maxlevels=0, force=False, quiet=0, - legacy=False, optimize=-1, - invalidation_mode=None): - """Byte-compile all module on sys.path. - - Arguments (all optional): - - skip_curdir: if true, skip current directory (default True) - maxlevels: max recursion level (default 0) - force: as for compile_dir() (default False) - quiet: as for compile_dir() (default 0) - legacy: as for compile_dir() (default False) - optimize: as for compile_dir() (default -1) - invalidation_mode: as for compiler_dir() - """ - success = True - for dir in sys.path: - if (not dir or dir == os.curdir) and skip_curdir: - if quiet < 2: - print('Skipping current directory') - else: - success = success and compile_dir( - dir, - maxlevels, - None, - force, - quiet=quiet, - legacy=legacy, - optimize=optimize, - invalidation_mode=invalidation_mode, - ) - return success - - -def main(): - """Script main program.""" - import argparse - - parser = argparse.ArgumentParser( - description='Utilities to support installing Python libraries.') - parser.add_argument('-l', action='store_const', const=0, - default=10, dest='maxlevels', - help="don't recurse into subdirectories") - parser.add_argument('-r', type=int, dest='recursion', - help=('control the maximum recursion level. ' - 'if `-l` and `-r` options are specified, ' - 'then `-r` takes precedence.')) - parser.add_argument('-f', action='store_true', dest='force', - help='force rebuild even if timestamps are up to date') - parser.add_argument('-q', action='count', dest='quiet', default=0, - help='output only error messages; -qq will suppress ' - 'the error messages as well.') - parser.add_argument('-b', action='store_true', dest='legacy', - help='use legacy (pre-PEP3147) compiled file locations') - parser.add_argument('-d', metavar='DESTDIR', dest='ddir', default=None, - help=('directory to prepend to file paths for use in ' - 'compile-time tracebacks and in runtime ' - 'tracebacks in cases where the source file is ' - 'unavailable')) - parser.add_argument('-x', metavar='REGEXP', dest='rx', default=None, - help=('skip files matching the regular expression; ' - 'the regexp is searched for in the full path ' - 'of each file considered for compilation')) - parser.add_argument('-i', metavar='FILE', dest='flist', - help=('add all the files and directories listed in ' - 'FILE to the list considered for compilation; ' - 'if "-", names are read from stdin')) - parser.add_argument('compile_dest', metavar='FILE|DIR', nargs='*', - help=('zero or more file and directory names ' - 'to compile; if no arguments given, defaults ' - 'to the equivalent of -l sys.path')) - parser.add_argument('-j', '--workers', default=1, - type=int, help='Run compileall concurrently') - invalidation_modes = [mode.name.lower().replace('_', '-') - for mode in py_compile.PycInvalidationMode] - parser.add_argument('--invalidation-mode', - choices=sorted(invalidation_modes), - help=('set .pyc invalidation mode; defaults to ' - '"checked-hash" if the SOURCE_DATE_EPOCH ' - 'environment variable is set, and ' - '"timestamp" otherwise.')) - - args = parser.parse_args() - compile_dests = args.compile_dest - - if args.rx: - import re - args.rx = re.compile(args.rx) - - - if args.recursion is not None: - maxlevels = args.recursion - else: - maxlevels = args.maxlevels - - # if flist is provided then load it - if args.flist: - try: - with (sys.stdin if args.flist=='-' else open(args.flist)) as f: - for line in f: - compile_dests.append(line.strip()) - except OSError: - if args.quiet < 2: - print("Error reading file list {}".format(args.flist)) - return False - - if args.invalidation_mode: - ivl_mode = args.invalidation_mode.replace('-', '_').upper() - invalidation_mode = py_compile.PycInvalidationMode[ivl_mode] - else: - invalidation_mode = None - - success = True - try: - if compile_dests: - for dest in compile_dests: - if os.path.isfile(dest): - if not compile_file(dest, args.ddir, args.force, args.rx, - args.quiet, args.legacy, - invalidation_mode=invalidation_mode): - success = False - else: - if not compile_dir(dest, maxlevels, args.ddir, - args.force, args.rx, args.quiet, - args.legacy, workers=args.workers, - invalidation_mode=invalidation_mode): - success = False - return success - else: - return compile_path(legacy=args.legacy, force=args.force, - quiet=args.quiet, - invalidation_mode=invalidation_mode) - except KeyboardInterrupt: - if args.quiet < 2: - print("\n[interrupted]") - return False - return True - - -if __name__ == '__main__': - exit_status = int(not main()) - sys.exit(exit_status) diff --git a/dist/lib/concurrent/__init__.py b/dist/lib/concurrent/__init__.py deleted file mode 100644 index 196d378..0000000 --- a/dist/lib/concurrent/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# This directory is a Python package. diff --git a/dist/lib/concurrent/__pycache__/__init__.cpython-38.opt-1.pyc b/dist/lib/concurrent/__pycache__/__init__.cpython-38.opt-1.pyc deleted file mode 100644 index cb37ba0..0000000 Binary files a/dist/lib/concurrent/__pycache__/__init__.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/concurrent/futures/__init__.py b/dist/lib/concurrent/futures/__init__.py deleted file mode 100644 index d746aea..0000000 --- a/dist/lib/concurrent/futures/__init__.py +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright 2009 Brian Quinlan. All Rights Reserved. -# Licensed to PSF under a Contributor Agreement. - -"""Execute computations asynchronously using threads or processes.""" - -__author__ = 'Brian Quinlan (brian@sweetapp.com)' - -from concurrent.futures._base import (FIRST_COMPLETED, - FIRST_EXCEPTION, - ALL_COMPLETED, - CancelledError, - TimeoutError, - InvalidStateError, - BrokenExecutor, - Future, - Executor, - wait, - as_completed) - -__all__ = ( - 'FIRST_COMPLETED', - 'FIRST_EXCEPTION', - 'ALL_COMPLETED', - 'CancelledError', - 'TimeoutError', - 'BrokenExecutor', - 'Future', - 'Executor', - 'wait', - 'as_completed', - 'ProcessPoolExecutor', - 'ThreadPoolExecutor', -) - - -def __dir__(): - return __all__ + ('__author__', '__doc__') - - -def __getattr__(name): - global ProcessPoolExecutor, ThreadPoolExecutor - - if name == 'ProcessPoolExecutor': - from .process import ProcessPoolExecutor as pe - ProcessPoolExecutor = pe - return pe - - if name == 'ThreadPoolExecutor': - from .thread import ThreadPoolExecutor as te - ThreadPoolExecutor = te - return te - - raise AttributeError(f"module {__name__} has no attribute {name}") diff --git a/dist/lib/concurrent/futures/__pycache__/__init__.cpython-38.opt-1.pyc b/dist/lib/concurrent/futures/__pycache__/__init__.cpython-38.opt-1.pyc deleted file mode 100644 index 1d1acff..0000000 Binary files a/dist/lib/concurrent/futures/__pycache__/__init__.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/concurrent/futures/__pycache__/_base.cpython-38.opt-1.pyc b/dist/lib/concurrent/futures/__pycache__/_base.cpython-38.opt-1.pyc deleted file mode 100644 index a007e39..0000000 Binary files a/dist/lib/concurrent/futures/__pycache__/_base.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/concurrent/futures/__pycache__/process.cpython-38.opt-1.pyc b/dist/lib/concurrent/futures/__pycache__/process.cpython-38.opt-1.pyc deleted file mode 100644 index 2b1efad..0000000 Binary files a/dist/lib/concurrent/futures/__pycache__/process.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/concurrent/futures/__pycache__/thread.cpython-38.opt-1.pyc b/dist/lib/concurrent/futures/__pycache__/thread.cpython-38.opt-1.pyc deleted file mode 100644 index a12207a..0000000 Binary files a/dist/lib/concurrent/futures/__pycache__/thread.cpython-38.opt-1.pyc and /dev/null differ diff --git a/dist/lib/concurrent/futures/_base.py b/dist/lib/concurrent/futures/_base.py deleted file mode 100644 index 6001e3b..0000000 --- a/dist/lib/concurrent/futures/_base.py +++ /dev/null @@ -1,643 +0,0 @@ -# Copyright 2009 Brian Quinlan. All Rights Reserved. -# Licensed to PSF under a Contributor Agreement. - -__author__ = 'Brian Quinlan (brian@sweetapp.com)' - -import collections -import logging -import threading -import time - -FIRST_COMPLETED = 'FIRST_COMPLETED' -FIRST_EXCEPTION = 'FIRST_EXCEPTION' -ALL_COMPLETED = 'ALL_COMPLETED' -_AS_COMPLETED = '_AS_COMPLETED' - -# Possible future states (for internal use by the futures package). -PENDING = 'PENDING' -RUNNING = 'RUNNING' -# The future was cancelled by the user... -CANCELLED = 'CANCELLED' -# ...and _Waiter.add_cancelled() was called by a worker. -CANCELLED_AND_NOTIFIED = 'CANCELLED_AND_NOTIFIED' -FINISHED = 'FINISHED' - -_FUTURE_STATES = [ - PENDING, - RUNNING, - CANCELLED, - CANCELLED_AND_NOTIFIED, - FINISHED -] - -_STATE_TO_DESCRIPTION_MAP = { - PENDING: "pending", - RUNNING: "running", - CANCELLED: "cancelled", - CANCELLED_AND_NOTIFIED: "cancelled", - FINISHED: "finished" -} - -# Logger for internal use by the futures package. -LOGGER = logging.getLogger("concurrent.futures") - -class Error(Exception): - """Base class for all future-related exceptions.""" - pass - -class CancelledError(Error): - """The Future was cancelled.""" - pass - -class TimeoutError(Error): - """The operation exceeded the given deadline.""" - pass - -class InvalidStateError(Error): - """The operation is not allowed in this state.""" - pass - -class _Waiter(object): - """Provides the event that wait() and as_completed() block on.""" - def __init__(self): - self.event = threading.Event() - self.finished_futures = [] - - def add_result(self, future): - self.finished_futures.append(future) - - def add_exception(self, future): - self.finished_futures.append(future) - - def add_cancelled(self, future): - self.finished_futures.append(future) - -class _AsCompletedWaiter(_Waiter): - """Used by as_completed().""" - - def __init__(self): - super(_AsCompletedWaiter, self).__init__() - self.lock = threading.Lock() - - def add_result(self, future): - with self.lock: - super(_AsCompletedWaiter, self).add_result(future) - self.event.set() - - def add_exception(self, future): - with self.lock: - super(_AsCompletedWaiter, self).add_exception(future) - self.event.set() - - def add_cancelled(self, future): - with self.lock: - super(_AsCompletedWaiter, self).add_cancelled(future) - self.event.set() - -class _FirstCompletedWaiter(_Waiter): - """Used by wait(return_when=FIRST_COMPLETED).""" - - def add_result(self, future): - super().add_result(future) - self.event.set() - - def add_exception(self, future): - super().add_exception(future) - self.event.set() - - def add_cancelled(self, future): - super().add_cancelled(future) - self.event.set() - -class _AllCompletedWaiter(_Waiter): - """Used by wait(return_when=FIRST_EXCEPTION and ALL_COMPLETED).""" - - def __init__(self, num_pending_calls, stop_on_exception): - self.num_pending_calls = num_pending_calls - self.stop_on_exception = stop_on_exception - self.lock = threading.Lock() - super().__init__() - - def _decrement_pending_calls(self): - with self.lock: - self.num_pending_calls -= 1 - if not self.num_pending_calls: - self.event.set() - - def add_result(self, future): - super().add_result(future) - self._decrement_pending_calls() - - def add_exception(self, future): - super().add_exception(future) - if self.stop_on_exception: - self.event.set() - else: - self._decrement_pending_calls() - - def add_cancelled(self, future): - super().add_cancelled(future) - self._decrement_pending_calls() - -class _AcquireFutures(object): - """A context manager that does an ordered acquire of Future conditions.""" - - def __init__(self, futures): - self.futures = sorted(futures, key=id) - - def __enter__(self): - for future in self.futures: - future._condition.acquire() - - def __exit__(self, *args): - for future in self.futures: - future._condition.release() - -def _create_and_install_waiters(fs, return_when): - if return_when == _AS_COMPLETED: - waiter = _AsCompletedWaiter() - elif return_when == FIRST_COMPLETED: - waiter = _FirstCompletedWaiter() - else: - pending_count = sum( - f._state not in [CANCELLED_AND_NOTIFIED, FINISHED] for f in fs) - - if return_when == FIRST_EXCEPTION: - waiter = _AllCompletedWaiter(pending_count, stop_on_exception=True) - elif return_when == ALL_COMPLETED: - waiter = _AllCompletedWaiter(pending_count, stop_on_exception=False) - else: - raise ValueError("Invalid return condition: %r" % return_when) - - for f in fs: - f._waiters.append(waiter) - - return waiter - - -def _yield_finished_futures(fs, waiter, ref_collect): - """ - Iterate on the list *fs*, yielding finished futures one by one in - reverse order. - Before yielding a future, *waiter* is removed from its waiters - and the future is removed from each set in the collection of sets - *ref_collect*. - - The aim of this function is to avoid keeping stale references after - the future is yielded and before the iterator resumes. - """ - while fs: - f = fs[-1] - for futures_set in ref_collect: - futures_set.remove(f) - with f._condition: - f._waiters.remove(waiter) - del f - # Careful not to keep a reference to the popped value - yield fs.pop() - - -def as_completed(fs, timeout=None): - """An iterator over the given futures that yields each as it completes. - - Args: - fs: The sequence of Futures (possibly created by different Executors) to - iterate over. - timeout: The maximum number of seconds to wait. If None, then there - is no limit on the wait time. - - Returns: - An iterator that yields the given Futures as they complete (finished or - cancelled). If any given Futures are duplicated, they will be returned - once. - - Raises: - TimeoutError: If the entire result iterator could not be generated - before the given timeout. - """ - if timeout is not None: - end_time = timeout + time.monotonic() - - fs = set(fs) - total_futures = len(fs) - with _AcquireFutures(fs): - finished = set( - f for f in fs - if f._state in [CANCELLED_AND_NOTIFIED, FINISHED]) - pending = fs - finished - waiter = _create_and_install_waiters(fs, _AS_COMPLETED) - finished = list(finished) - try: - yield from _yield_finished_futures(finished, waiter, - ref_collect=(fs,)) - - while pending: - if timeout is None: - wait_timeout = None - else: - wait_timeout = end_time - time.monotonic() - if wait_timeout < 0: - raise TimeoutError( - '%d (of %d) futures unfinished' % ( - len(pending), total_futures)) - - waiter.event.wait(wait_timeout) - - with waiter.lock: - finished = waiter.finished_futures - waiter.finished_futures = [] - waiter.event.clear() - - # reverse to keep finishing order - finished.reverse() - yield from _yield_finished_futures(finished, waiter, - ref_collect=(fs, pending)) - - finally: - # Remove waiter from unfinished futures - for f in fs: - with f._condition: - f._waiters.remove(waiter) - -DoneAndNotDoneFutures = collections.namedtuple( - 'DoneAndNotDoneFutures', 'done not_done') -def wait(fs, timeout=None, return_when=ALL_COMPLETED): - """Wait for the futures in the given sequence to complete. - - Args: - fs: The sequence of Futures (possibly created by different Executors) to - wait upon. - timeout: The maximum number of seconds to wait. If None, then there - is no limit on the wait time. - return_when: Indicates when this function should return. The options - are: - - FIRST_COMPLETED - Return when any future finishes or is - cancelled. - FIRST_EXCEPTION - Return when any future finishes by raising an - exception. If no future raises an exception - then it is equivalent to ALL_COMPLETED. - ALL_COMPLETED - Return when all futures finish or are cancelled. - - Returns: - A named 2-tuple of sets. The first set, named 'done', contains the - futures that completed (is finished or cancelled) before the wait - completed. The second set, named 'not_done', contains uncompleted - futures. - """ - with _AcquireFutures(fs): - done = set(f for f in fs - if f._state in [CANCELLED_AND_NOTIFIED, FINISHED]) - not_done = set(fs) - done - - if (return_when == FIRST_COMPLETED) and done: - return DoneAndNotDoneFutures(done, not_done) - elif (return_when == FIRST_EXCEPTION) and done: - if any(f for f in done - if not f.cancelled() and f.exception() is not None): - return DoneAndNotDoneFutures(done, not_done) - - if len(done) == len(fs): - return DoneAndNotDoneFutures(done, not_done) - - waiter = _create_and_install_waiters(fs, return_when) - - waiter.event.wait(timeout) - for f in fs: - with f._condition: - f._waiters.remove(waiter) - - done.update(waiter.finished_futures) - return DoneAndNotDoneFutures(done, set(fs) - done) - -class Future(object): - """Represents the result of an asynchronous computation.""" - - def __init__(self): - """Initializes the future. Should not be called by clients.""" - self._condition = threading.Condition() - self._state = PENDING - self._result = None - self._exception = None - self._waiters = [] - self._done_callbacks = [] - - def _invoke_callbacks(self): - for callback in self._done_callbacks: - try: - callback(self) - except Exception: - LOGGER.exception('exception calling callback for %r', self) - - def __repr__(self): - with self._condition: - if self._state == FINISHED: - if self._exception: - return '<%s at %#x state=%s raised %s>' % ( - self.__class__.__name__, - id(self), - _STATE_TO_DESCRIPTION_MAP[self._state], - self._exception.__class__.__name__) - else: - return '<%s at %#x state=%s returned %s>' % ( - self.__class__.__name__, - id(self), - _STATE_TO_DESCRIPTION_MAP[self._state], - self._result.__class__.__name__) - return '<%s at %#x state=%s>' % ( - self.__class__.__name__, - id(self), - _STATE_TO_DESCRIPTION_MAP[self._state]) - - def cancel(self): - """Cancel the future if possible. - - Returns True if the future was cancelled, False otherwise. A future - cannot be cancelled if it is running or has already completed. - """ - with self._condition: - if self._state in [RUNNING, FINISHED]: - return False - - if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]: - return True - - self._state = CANCELLED - self._condition.notify_all() - - self._invoke_callbacks() - return True - - def cancelled(self): - """Return True if the future was cancelled.""" - with self._condition: - return self._state in [CANCELLED, CANCELLED_AND_NOTIFIED] - - def running(self): - """Return True if the future is currently executing.""" - with self._condition: - return self._state == RUNNING - - def done(self): - """Return True of the future was cancelled or finished executing.""" - with self._condition: - return self._state in [CANCELLED, CANCELLED_AND_NOTIFIED, FINISHED] - - def __get_result(self): - if self._exception: - raise self._exception - else: - return self._result - - def add_done_callback(self, fn): - """Attaches a callable that will be called when the future finishes. - - Args: - fn: A callable that will be called with this future as its only - argument when the future completes or is cancelled. The callable - will always be called by a thread in the same process in which - it was added. If the future has already completed or been - cancelled then the callable will be called immediately. These - callables are called in the order that they were added. - """ - with self._condition: - if self._state not in [CANCELLED, CANCELLED_AND_NOTIFIED, FINISHED]: - self._done_callbacks.append(fn) - return - try: - fn(self) - except Exception: - LOGGER.exception('exception calling callback for %r', self) - - def result(self, timeout=None): - """Return the result of the call that the future represents. - - Args: - timeout: The number of seconds to wait for the result if the future - isn't done. If None, then there is no limit on the wait time. - - Returns: - The result of the call that the future represents. - - Raises: - CancelledError: If the future was cancelled. - TimeoutError: If the future didn't finish executing before the given - timeout. - Exception: If the call raised then that exception will be raised. - """ - with self._condition: - if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]: - raise CancelledError() - elif self._state == FINISHED: - return self.__get_result() - - self._condition.wait(timeout) - - if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]: - raise CancelledError() - elif self._state == FINISHED: - return self.__get_result() - else: - raise TimeoutError() - - def exception(self, timeout=None): - """Return the exception raised by the call that the future represents. - - Args: - timeout: The number of seconds to wait for the exception if the - future isn't done. If None, then there is no limit on the wait - time. - - Returns: - The exception raised by the call that the future represents or None - if the call completed without raising. - - Raises: - CancelledError: If the future was cancelled. - TimeoutError: If the future didn't finish executing before the given - timeout. - """ - - with self._condition: - if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]: - raise CancelledError() - elif self._state == FINISHED: - return self._exception - - self._condition.wait(timeout) - - if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]: - raise CancelledError() - elif self._state == FINISHED: - return self._exception - else: - raise TimeoutError() - - # The following methods should only be used by Executors and in tests. - def set_running_or_notify_cancel(self): - """Mark the future as running or process any cancel notifications. - - Should only be used by Executor implementations and unit tests. - - If the future has been cancelled (cancel() was called and returned - True) then any threads waiting on the future completing (though calls - to as_completed() or wait()) are notified and False is returned. - - If the future was not cancelled then it is put in the running state - (future calls to running() will return True) and True is returned. - - This method should be called by Executor implementations before - executing the work associated with this future. If this method returns - False then the work should not be executed. - - Returns: - False if the Future was cancelled, True otherwise. - - Raises: - RuntimeError: if this method was already called or if set_result() - or set_exception() was called. - """ - with self._condition: - if self._state == CANCELLED: - self._state = CANCELLED_AND_NOTIFIED - for waiter in self._waiters: - waiter.add_cancelled(self) - # self._condition.notify_all() is not necessary because - # self.cancel() triggers a notification. - return False - elif self._state == PENDING: - self._state = RUNNING - return True - else: - LOGGER.critical('Future %s in unexpected state: %s', - id(self), - self._state) - raise RuntimeError('Future in unexpected state') - - def set_result(self, result): - """Sets the return value of work associated with the future. - - Should only be used by Executor implementations and unit tests. - """ - with self._condition: - if self._state in {CANCELLED, CANCELLED_AND_NOTIFIED, FINISHED}: - raise InvalidStateError('{}: {!r}'.format(self._state, self)) - self._result = result - self._state = FINISHED - for waiter in self._waiters: - waiter.add_result(self) - self._condition.notify_all() - self._invoke_callbacks() - - def set_exception(self, exception): - """Sets the result of the future as being the given exception. - - Should only be used by Executor implementations and unit tests. - """ - with self._condition: - if self._state in {CANCELLED, CANCELLED_AND_NOTIFIED, FINISHED}: - raise InvalidStateError('{}: {!r}'.format(self._state, self)) - self._exception = exception - self._state = FINISHED - for waiter in self._waiters: - waiter.add_exception(self) - self._condition.notify_all() - self._invoke_callbacks() - -class Executor(object): - """This is an abstract base class for concrete asynchronous executors.""" - - def submit(*args, **kwargs): - """Submits a callable to be executed with the given arguments. - - Schedules the callable to be executed as fn(*args, **kwargs) and returns - a Future instance representing the execution of the callable. - - Returns: - A Future representing the given call. - """ - if len(args) >= 2: - pass - elif not args: - raise TypeError("descriptor 'submit' of 'Executor' object " - "needs an argument") - elif 'fn' in kwargs: - import warnings - warnings.warn("Passing 'fn' as keyword argument is deprecated", - DeprecationWarning, stacklevel=2) - else: - raise TypeError('submit expected at least 1 positional argument, ' - 'got %d' % (len(args)-1)) - - raise NotImplementedError() - submit.__text_signature__ = '($self, fn, /, *args, **kwargs)' - - def map(self, fn, *iterables, timeout=None, chunksize=1): - """Returns an iterator equivalent to map(fn, iter). - - Args: - fn: A callable that will take as many arguments as there are - passed iterables. - timeout: The maximum number of seconds to wait. If None, then there - is no limit on the wait time. - chunksize: The size of the chunks the iterable will be broken into - before being passed to a child process. This argument is only - used by ProcessPoolExecutor; it is ignored by - ThreadPoolExecutor. - - Returns: - An iterator equivalent to: map(func, *iterables) but the calls may - be evaluated out-of-order. - - Raises: - TimeoutError: If the entire result iterator could not be generated - before the given timeout. - Exception: If fn(*args) raises for any values. - """ - if timeout is not None: - end_time = timeout + time.monotonic() - - fs = [self.submit(fn, *args) for args in zip(*iterables)] - - # Yield must be hidden in closure so that the futures are submitted - # before the first iterator value is required. - def result_iterator(): - try: - # reverse to keep finishing order - fs.reverse() - while fs: - # Careful not to keep a reference to the popped future - if timeout is None: - yield fs.pop().result() - else: - yield fs.pop().result(end_time - time.monotonic()) - finally: - for future in fs: - future.cancel() - return result_iterator() - - def shutdown(self, wait=True): - """Clean-up the resources associated with the Executor. - - It is safe to call this method several times. Otherwise, no other - methods can be called after this one. - - Args: - wait: If True then shutdown will not return until all running - futures have finished executing and the resources used by the - executor have been reclaimed. - """ - pass - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - self.shutdown(wait=True) - return False - - -class BrokenExecutor(RuntimeError): - """ - Raised when a executor has become non-functional after a severe failure. - """ diff --git a/dist/lib/concurrent/futures/process.py b/dist/lib/concurrent/futures/process.py deleted file mode 100644 index 2b2b78e..0000000 --- a/dist/lib/concurrent/futures/process.py +++ /dev/null @@ -1,704 +0,0 @@ -# Copyright 2009 Brian Quinlan. All Rights Reserved. -# Licensed to PSF under a Contributor Agreement. - -"""Implements ProcessPoolExecutor. - -The following diagram and text describe the data-flow through the system: - -|======================= In-process =====================|== Out-of-process ==| - -+----------+ +----------+ +--------+ +-----------+ +---------+ -| | => | Work Ids | | | | Call Q | | Process | -| | +----------+ | | +-----------+ | Pool | -| | | ... | | | | ... | +---------+ -| | | 6 | => | | => | 5, call() | => | | -| | | 7 | | | | ... | | | -| Process | | ... | | Local | +-----------+ | Process | -| Pool | +----------+ | Worker | | #1..n | -| Executor | | Thread | | | -| | +----------- + | | +-----------+ | | -| | <=> | Work Items | <=> | | <= | Result Q | <= | | -| | +------------+ | | +-----------+ | | -| | | 6: call() | | | | ... | | | -| | | future | | | | 4, result | | | -| | | ... | | | | 3, except | | | -+----------+ +------------+ +--------+ +-----------+ +---------+ - -Executor.submit() called: -- creates a uniquely numbered _WorkItem and adds it to the "Work Items" dict -- adds the id of the _WorkItem to the "Work Ids" queue - -Local worker thread: -- reads work ids from the "Work Ids" queue and looks up the corresponding - WorkItem from the "Work Items" dict: if the work item has been cancelled then - it is simply removed from the dict, otherwise it is repackaged as a - _CallItem and put in the "Call Q". New _CallItems are put in the "Call Q" - until "Call Q" is full. NOTE: the size of the "Call Q" is kept small because - calls placed in the "Call Q" can no longer be cancelled with Future.cancel(). -- reads _ResultItems from "Result Q", updates the future stored in the - "Work Items" dict and deletes the dict entry - -Process #1..n: -- reads _CallItems from "Call Q", executes the calls, and puts the resulting - _ResultItems in "Result Q" -""" - -__author__ = 'Brian Quinlan (brian@sweetapp.com)' - -import atexit -import os -from concurrent.futures import _base -import queue -from queue import Full -import multiprocessing as mp -import multiprocessing.connection -from multiprocessing.queues import Queue -import threading -import weakref -from functools import partial -import itertools -import sys -import traceback - -# Workers are created as daemon threads and processes. This is done to allow the -# interpreter to exit when there are still idle processes in a -# ProcessPoolExecutor's process pool (i.e. shutdown() was not called). However, -# allowing workers to die with the interpreter has two undesirable properties: -# - The workers would still be running during interpreter shutdown, -# meaning that they would fail in unpredictable ways. -# - The workers could be killed while evaluating a work item, which could -# be bad if the callable being evaluated has external side-effects e.g. -# writing to a file. -# -# To work around this problem, an exit handler is installed which tells the -# workers to exit when their work queues are empty and then waits until the -# threads/processes finish. - -_threads_wakeups = weakref.WeakKeyDictionary() -_global_shutdown = False - - -class _ThreadWakeup: - def __init__(self): - self._reader, self._writer = mp.Pipe(duplex=False) - - def close(self): - self._writer.close() - self._reader.close() - - def wakeup(self): - self._writer.send_bytes(b"") - - def clear(self): - while self._reader.poll(): - self._reader.recv_bytes() - - -def _python_exit(): - global _global_shutdown - _global_shutdown = True - items = list(_threads_wakeups.items()) - for _, thread_wakeup in items: - thread_wakeup.wakeup() - for t, _ in items: - t.join() - -# Controls how many more calls than processes will be queued in the call queue. -# A smaller number will mean that processes spend more time idle waiting for -# work while a larger number will make Future.cancel() succeed less frequently -# (Futures in the call queue cannot be cancelled). -EXTRA_QUEUED_CALLS = 1 - - -# On Windows, WaitForMultipleObjects is used to wait for processes to finish. -# It can wait on, at most, 63 objects. There is an overhead of two objects: -# - the result queue reader -# - the thread wakeup reader -_MAX_WINDOWS_WORKERS = 63 - 2 - -# Hack to embed stringification of remote traceback in local traceback - -class _RemoteTraceback(Exception): - def __init__(self, tb): - self.tb = tb - def __str__(self): - return self.tb - -class _ExceptionWithTraceback: - def __init__(self, exc, tb): - tb = traceback.format_exception(type(exc), exc, tb) - tb = ''.join(tb) - self.exc = exc - self.tb = '\n"""\n%s"""' % tb - def __reduce__(self): - return _rebuild_exc, (self.exc, self.tb) - -def _rebuild_exc(exc, tb): - exc.__cause__ = _RemoteTraceback(tb) - return exc - -class _WorkItem(object): - def __init__(self, future, fn, args, kwargs): - self.future = future - self.fn = fn - self.args = args - self.kwargs = kwargs - -class _ResultItem(object): - def __init__(self, work_id, exception=None, result=None): - self.work_id = work_id - self.exception = exception - self.result = result - -class _CallItem(object): - def __init__(self, work_id, fn, args, kwargs): - self.work_id = work_id - self.fn = fn - self.args = args - self.kwargs = kwargs - - -class _SafeQueue(Queue): - """Safe Queue set exception to the future object linked to a job""" - def __init__(self, max_size=0, *, ctx, pending_work_items): - self.pending_work_items = pending_work_items - super().__init__(max_size, ctx=ctx) - - def _on_queue_feeder_error(self, e, obj): - if isinstance(obj, _CallItem): - tb = traceback.format_exception(type(e), e, e.__traceback__) - e.__cause__ = _RemoteTraceback('\n"""\n{}"""'.format(''.join(tb))) - work_item = self.pending_work_items.pop(obj.work_id, None) - # work_item can be None if another process terminated. In this case, - # the queue_manager_thread fails all work_items with BrokenProcessPool - if work_item is not None: - work_item.future.set_exception(e) - else: - super()._on_queue_feeder_error(e, obj) - - -def _get_chunks(*iterables, chunksize): - """ Iterates over zip()ed iterables in chunks. """ - it = zip(*iterables) - while True: - chunk = tuple(itertools.islice(it, chunksize)) - if not chunk: - return - yield chunk - -def _process_chunk(fn, chunk): - """ Processes a chunk of an iterable passed to map. - - Runs the function passed to map() on a chunk of the - iterable passed to map. - - This function is run in a separate process. - - """ - return [fn(*args) for args in chunk] - - -def _sendback_result(result_queue, work_id, result=None, exception=None): - """Safely send back the given result or exception""" - try: - result_queue.put(_ResultItem(work_id, result=result, - exception=exception)) - except BaseException as e: - exc = _ExceptionWithTraceback(e, e.__traceback__) - result_queue.put(_ResultItem(work_id, exception=exc)) - - -def _process_worker(call_queue, result_queue, initializer, initargs): - """Evaluates calls from call_queue and places the results in result_queue. - - This worker is run in a separate process. - - Args: - call_queue: A ctx.Queue of _CallItems that will be read and - evaluated by the worker. - result_queue: A ctx.Queue of _ResultItems that will written - to by the worker. - initializer: A callable initializer, or None - initargs: A tuple of args for the initializer - """ - if initializer is not None: - try: - initializer(*initargs) - except BaseException: - _base.LOGGER.critical('Exception in initializer:', exc_info=True) - # The parent will notice that the process stopped and - # mark the pool broken - return - while True: - call_item = call_queue.get(block=True) - if call_item is None: - # Wake up queue management thread - result_queue.put(os.getpid()) - return - try: - r = call_item.fn(*call_item.args, **call_item.kwargs) - except BaseException as e: - exc = _ExceptionWithTraceback(e, e.__traceback__) - _sendback_result(result_queue, call_item.work_id, exception=exc) - else: - _sendback_result(result_queue, call_item.work_id, result=r) - del r - - # Liberate the resource as soon as possible, to avoid holding onto - # open files or shared memory that is not needed anymore - del call_item - - -def _add_call_item_to_queue(pending_work_items, - work_ids, - call_queue): - """Fills call_queue with _WorkItems from pending_work_items. - - This function never blocks. - - Args: - pending_work_items: A dict mapping work ids to _WorkItems e.g. - {5: <_WorkItem...>, 6: <_WorkItem...>, ...} - work_ids: A queue.Queue of work ids e.g. Queue([5, 6, ...]). Work ids - are consumed and the corresponding _WorkItems from - pending_work_items are transformed into _CallItems and put in - call_queue. - call_queue: A multiprocessing.Queue that will be filled with _CallItems - derived from _WorkItems. - """ - while True: - if call_queue.full(): - return - try: - work_id = work_ids.get(block=False) - except queue.Empty: - return - else: - work_item = pending_work_items[work_id] - - if work_item.future.set_running_or_notify_cancel(): - call_queue.put(_CallItem(work_id, - work_item.fn, - work_item.args, - work_item.kwargs), - block=True) - else: - del pending_work_items[work_id] - continue - - -def _queue_management_worker(executor_reference, - processes, - pending_work_items, - work_ids_queue, - call_queue, - result_queue, - thread_wakeup): - """Manages the communication between this process and the worker processes. - - This function is run in a local thread. - - Args: - executor_reference: A weakref.ref to the ProcessPoolExecutor that owns - this thread. Used to determine if the ProcessPoolExecutor has been - garbage collected and that this function can exit. - process: A list of the ctx.Process instances used as - workers. - pending_work_items: A dict mapping work ids to _WorkItems e.g. - {5: <_WorkItem...>, 6: <_WorkItem...>, ...} - work_ids_queue: A queue.Queue of work ids e.g. Queue([5, 6, ...]). - call_queue: A ctx.Queue that will be filled with _CallItems - derived from _WorkItems for processing by the process workers. - result_queue: A ctx.SimpleQueue of _ResultItems generated by the - process workers. - thread_wakeup: A _ThreadWakeup to allow waking up the - queue_manager_thread from the main Thread and avoid deadlocks - caused by permanently locked queues. - """ - executor = None - - def shutting_down(): - return (_global_shutdown or executor is None - or executor._shutdown_thread) - - def shutdown_worker(): - # This is an upper bound on the number of children alive. - n_children_alive = sum(p.is_alive() for p in processes.values()) - n_children_to_stop = n_children_alive - n_sentinels_sent = 0 - # Send the right number of sentinels, to make sure all children are - # properly terminated. - while n_sentinels_sent < n_children_to_stop and n_children_alive > 0: - for i in range(n_children_to_stop - n_sentinels_sent): - try: - call_queue.put_nowait(None) - n_sentinels_sent += 1 - except Full: - break - n_children_alive = sum(p.is_alive() for p in processes.values()) - - # Release the queue's resources as soon as possible. - call_queue.close() - # If .join() is not called on the created processes then - # some ctx.Queue methods may deadlock on Mac OS X. - for p in processes.values(): - p.join() - - result_reader = result_queue._reader - wakeup_reader = thread_wakeup._reader - readers = [result_reader, wakeup_reader] - - while True: - _add_call_item_to_queue(pending_work_items, - work_ids_queue, - call_queue) - - # Wait for a result to be ready in the result_queue while checking - # that all worker processes are still running, or for a wake up - # signal send. The wake up signals come either from new tasks being - # submitted, from the executor being shutdown/gc-ed, or from the - # shutdown of the python interpreter. - worker_sentinels = [p.sentinel for p in processes.values()] - ready = mp.connection.wait(readers + worker_sentinels) - - cause = None - is_broken = True - if result_reader in ready: - try: - result_item = result_reader.recv() - is_broken = False - except BaseException as e: - cause = traceback.format_exception(type(e), e, e.__traceback__) - - elif wakeup_reader in ready: - is_broken = False - result_item = None - thread_wakeup.clear() - if is_broken: - # Mark the process pool broken so that submits fail right now. - executor = executor_reference() - if executor is not None: - executor._broken = ('A child process terminated ' - 'abruptly, the process pool is not ' - 'usable anymore') - executor._shutdown_thread = True - executor = None - bpe = BrokenProcessPool("A process in the process pool was " - "terminated abruptly while the future was " - "running or pending.") - if cause is not None: - bpe.__cause__ = _RemoteTraceback( - f"\n'''\n{''.join(cause)}'''") - # All futures in flight must be marked failed - for work_id, work_item in pending_work_items.items(): - work_item.future.set_exception(bpe) - # Delete references to object. See issue16284 - del work_item - pending_work_items.clear() - # Terminate remaining workers forcibly: the queues or their - # locks may be in a dirty state and block forever. - for p in processes.values(): - p.terminate() - shutdown_worker() - return - if isinstance(result_item, int): - # Clean shutdown of a worker using its PID - # (avoids marking the executor broken) - assert shutting_down() - p = processes.pop(result_item) - p.join() - if not processes: - shutdown_worker() - return - elif result_item is not None: - work_item = pending_work_items.pop(result_item.work_id, None) - # work_item can be None if another process terminated (see above) - if work_item is not None: - if result_item.exception: - work_item.future.set_exception(result_item.exception) - else: - work_item.future.set_result(result_item.result) - # Delete references to object. See issue16284 - del work_item - # Delete reference to result_item - del result_item - - # Check whether we should start shutting down. - executor = executor_reference() - # No more work items can be added if: - # - The interpreter is shutting down OR - # - The executor that owns this worker has been collected OR - # - The executor that owns this worker has been shutdown. - if shutting_down(): - try: - # Flag the executor as shutting down as early as possible if it - # is not gc-ed yet. - if executor is not None: - executor._shutdown_thread = True - # Since no new work items can be added, it is safe to shutdown - # this thread if there are no pending work items. - if not pending_work_items: - shutdown_worker() - return - except Full: - # This is not a problem: we will eventually be woken up (in - # result_queue.get()) and be able to send a sentinel again. - pass - executor = None - - -_system_limits_checked = False -_system_limited = None - - -def _check_system_limits(): - global _system_limits_checked, _system_limited - if _system_limits_checked: - if _system_limited: - raise NotImplementedError(_system_limited) - _system_limits_checked = True - try: - nsems_max = os.sysconf("SC_SEM_NSEMS_MAX") - except (AttributeError, ValueError): - # sysconf not available or setting not available - return - if nsems_max == -1: - # indetermined limit, assume that limit is determined - # by available memory only - return - if nsems_max >= 256: - # minimum number of semaphores available - # according to POSIX - return - _system_limited = ("system provides too few semaphores (%d" - " available, 256 necessary)" % nsems_max) - raise NotImplementedError(_system_limited) - - -def _chain_from_iterable_of_lists(iterable): - """ - Specialized implementation of itertools.chain.from_iterable. - Each item in *iterable* should be a list. This function is - careful not to keep references to yielded objects. - """ - for element in iterable: - element.reverse() - while element: - yield element.pop() - - -class BrokenProcessPool(_base.BrokenExecutor): - """ - Raised when a process in a ProcessPoolExecutor terminated abruptly - while a future was in the running state. - """ - - -class ProcessPoolExecutor(_base.Executor): - def __init__(self, max_workers=None, mp_context=None, - initializer=None, initargs=()): - """Initializes a new ProcessPoolExecutor instance. - - Args: - max_workers: The maximum number of processes that can be used to - execute the given calls. If None or not given then as many - worker processes will be created as the machine has processors. - mp_context: A multiprocessing context to launch the workers. This - object should provide SimpleQueue, Queue and Process. - initializer: A callable used to initialize worker processes. - initargs: A tuple of arguments to pass to the initializer. - """ - _check_system_limits() - - if max_workers is None: - self._max_workers = os.cpu_count() or 1 - if sys.platform == 'win32': - self._max_workers = min(_MAX_WINDOWS_WORKERS, - self._max_workers) - else: - if max_workers <= 0: - raise ValueError("max_workers must be greater than 0") - elif (sys.platform == 'win32' and - max_workers > _MAX_WINDOWS_WORKERS): - raise ValueError( - f"max_workers must be <= {_MAX_WINDOWS_WORKERS}") - - self._max_workers = max_workers - - if mp_context is None: - mp_context = mp.get_context() - self._mp_context = mp_context - - if initializer is not None and not callable(initializer): - raise TypeError("initializer must be a callable") - self._initializer = initializer - self._initargs = initargs - - # Management thread - self._queue_management_thread = None - - # Map of pids to processes - self._processes = {} - - # Shutdown is a two-step process. - self._shutdown_thread = False - self._shutdown_lock = threading.Lock() - self._broken = False - self._queue_count = 0 - self._pending_work_items = {} - - # Create communication channels for the executor - # Make the call queue slightly larger than the number of processes to - # prevent the worker processes from idling. But don't make it too big - # because futures in the call queue cannot be cancelled. - queue_size = self._max_workers + EXTRA_QUEUED_CALLS - self._call_queue = _SafeQueue( - max_size=queue_size, ctx=self._mp_context, - pending_work_items=self._pending_work_items) - # Killed worker processes can produce spurious "broken pipe" - # tracebacks in the queue's own worker thread. But we detect killed - # processes anyway, so silence the tracebacks. - self._call_queue._ignore_epipe = True - self._result_queue = mp_context.SimpleQueue() - self._work_ids = queue.Queue() - - # _ThreadWakeup is a communication channel used to interrupt the wait - # of the main loop of queue_manager_thread from another thread (e.g. - # when calling executor.submit or executor.shutdown). We do not use the - # _result_queue to send the wakeup signal to the queue_manager_thread - # as it could result in a deadlock if a worker process dies with the - # _result_queue write lock still acquired. - self._queue_management_thread_wakeup = _ThreadWakeup() - - def _start_queue_management_thread(self): - if self._queue_management_thread is None: - # When the executor gets garbarge collected, the weakref callback - # will wake up the queue management thread so that it can terminate - # if there is no pending work item. - def weakref_cb(_, - thread_wakeup=self._queue_management_thread_wakeup): - mp.util.debug('Executor collected: triggering callback for' - ' QueueManager wakeup') - thread_wakeup.wakeup() - # Start the processes so that their sentinels are known. - self._adjust_process_count() - self._queue_management_thread = threading.Thread( - target=_queue_management_worker, - args=(weakref.ref(self, weakref_cb), - self._processes, - self._pending_work_items, - self._work_ids, - self._call_queue, - self._result_queue, - self._queue_management_thread_wakeup), - name="QueueManagerThread") - self._queue_management_thread.daemon = True - self._queue_management_thread.start() - _threads_wakeups[self._queue_management_thread] = \ - self._queue_management_thread_wakeup - - def _adjust_process_count(self): - for _ in range(len(self._processes), self._max_workers): - p = self._mp_context.Process( - target=_process_worker, - args=(self._call_queue, - self._result_queue, - self._initializer, - self._initargs)) - p.start() - self._processes[p.pid] = p - - def submit(*args, **kwargs): - if len(args) >= 2: - self, fn, *args = args - elif not args: - raise TypeError("descriptor 'submit' of 'ProcessPoolExecutor' object " - "needs an argument") - elif 'fn' in kwargs: - fn = kwargs.pop('fn') - self, *args = args - import warnings - warnings.warn("Passing 'fn' as keyword argument is deprecated", - DeprecationWarning, stacklevel=2) - else: - raise TypeError('submit expected at least 1 positional argument, ' - 'got %d' % (len(args)-1)) - - with self._shutdown_lock: - if self._broken: - raise BrokenProcessPool(self._broken) - if self._shutdown_thread: - raise RuntimeError('cannot schedule new futures after shutdown') - if _global_shutdown: - raise RuntimeError('cannot schedule new futures after ' - 'interpreter shutdown') - - f = _base.Future() - w = _WorkItem(f, fn, args, kwargs) - - self._pending_work_items[self._queue_count] = w - self._work_ids.put(self._queue_count) - self._queue_count += 1 - # Wake up queue management thread - self._queue_management_thread_wakeup.wakeup() - - self._start_queue_management_thread() - return f - submit.__text_signature__ = _base.Executor.submit.__text_signature__ - submit.__doc__ = _base.Executor.submit.__doc__ - - def map(self, fn, *iterables, timeout=None, chunksize=1): - """Returns an iterator equivalent to map(fn, iter). - - Args: - fn: A callable that will take as many arguments as there are - passed iterables. - timeout: The maximum number of seconds to wait. If None, then there - is no limit on the wait time. - chunksize: If greater than one, the iterables will be chopped into - chunks of size chunksize and submitted to the process pool. - If set to one, the items in the list will be sent one at a time. - - Returns: - An iterator equivalent to: map(func, *iterables) but the calls may - be evaluated out-of-order. - - Raises: - TimeoutError: If the entire result iterator could not be generated - before the given timeout. - Exception: If fn(*args) raises for any values. - """ - if chunksize < 1: - raise ValueError("chunksize must be >= 1.") - - results = super().map(partial(_process_chunk, fn), - _get_chunks(*iterables, chunksize=chunksize), - timeout=timeout) - return _chain_from_iterable_of_lists(results) - - def shutdown(self, wait=True): - with self._shutdown_lock: - self._shutdown_thread = True - if self._queue_management_thread: - # Wake up queue management thread - self._queue_management_thread_wakeup.wakeup() - if wait: - self._queue_management_thread.join() - # To reduce the risk of opening too many files, remove references to - # objects that use file descriptors. - self._queue_management_thread = None - if self._call_queue is not None: - self._call_queue.close() - if wait: - self._call_queue.join_thread() - self._call_queue = None - self._result_queue = None - self._processes = None - - if self._queue_management_thread_wakeup: - self._queue_management_thread_wakeup.close() - self._queue_management_thread_wakeup = None - - shutdown.__doc__ = _base.Executor.shutdown.__doc__ - -atexit.register(_python_exit) diff --git a/dist/lib/concurrent/futures/thread.py b/dist/lib/concurrent/futures/thread.py deleted file mode 100644 index 9e669b2..0000000 --- a/dist/lib/concurrent/futures/thread.py +++ /dev/null @@ -1,237 +0,0 @@ -# Copyright 2009 Brian Quinlan. All Rights Reserved. -# Licensed to PSF under a Contributor Agreement. - -"""Implements ThreadPoolExecutor.""" - -__author__ = 'Brian Quinlan (brian@sweetapp.com)' - -import atexit -from concurrent.futures import _base -import itertools -import queue -import threading -import weakref -import os - -# Workers are created as daemon threads. This is done to allow the interpreter -# to exit when there are still idle threads in a ThreadPoolExecutor's thread -# pool (i.e. shutdown() was not called). However, allowing workers to die with -# the interpreter has two undesirable properties: -# - The workers would still be running during interpreter shutdown, -# meaning that they would fail in unpredictable ways. -# - The workers could be killed while evaluating a work item, which could -# be bad if the callable being evaluated has external side-effects e.g. -# writing to a file. -# -# To work around this problem, an exit handler is installed which tells the -# workers to exit when their work queues are empty and then waits until the -# threads finish. - -_threads_queues = weakref.WeakKeyDictionary() -_shutdown = False - -def _python_exit(): - global _shutdown - _shutdown = True - items = list(_threads_queues.items()) - for t, q in items: - q.put(None) - for t, q in items: - t.join() - -atexit.register(_python_exit) - - -class _WorkItem(object): - def __init__(self, future, fn, args, kwargs): - self.future = future - self.fn = fn - self.args = args - self.kwargs = kwargs - - def run(self): - if not self.future.set_running_or_notify_cancel(): - return - - try: - result = self.fn(*self.args, **self.kwargs) - except BaseException as exc: - self.future.set_exception(exc) - # Break a reference cycle with the exception 'exc' - self = None - else: - self.future.set_result(result) - - -def _worker(executor_reference, work_queue, initializer, initargs): - if initializer is not None: - try: - initializer(*initargs) - except BaseException: - _base.LOGGER.critical('Exception in initializer:', exc_info=True) - executor = executor_reference() - if executor is not None: - executor._initializer_failed() - return - try: - while True: - work_item = work_queue.get(block=True) - if work_item is not None: - work_item.run() - # Delete references to object. See issue16284 - del work_item - - # attempt to increment idle count - executor = executor_reference() - if executor is not None: - executor._idle_semaphore.release() - del executor - continue - - executor = executor_reference() - # Exit if: - # - The interpreter is shutting down OR - # - The executor that owns the worker has been collected OR - # - The executor that owns the worker has been shutdown. - if _shutdown or executor is None or executor._shutdown: - # Flag the executor as shutting down as early as possible if it - # is not gc-ed yet. - if executor is not None: - executor._shutdown = True - # Notice other workers - work_queue.put(None) - return - del executor - except BaseException: - _base.LOGGER.critical('Exception in worker', exc_info=True) - - -class BrokenThreadPool(_base.BrokenExecutor): - """ - Raised when a worker thread in a ThreadPoolExecutor failed initializing. - """ - - -class ThreadPoolExecutor(_base.Executor): - - # Used to assign unique thread names when thread_name_prefix is not supplied. - _counter = itertools.count().__next__ - - def __init__(self, max_workers=None, thread_name_prefix='', - initializer=None, initargs=()): - """Initializes a new ThreadPoolExecutor instance. - - Args: - max_workers: The maximum number of threads that can be used to - execute the given calls. - thread_name_prefix: An optional name prefix to give our threads. - initializer: A callable used to initialize worker threads. - initargs: A tuple of arguments to pass to the initializer. - """ - if max_workers is None: - # ThreadPoolExecutor is often used to: - # * CPU bound task which releases GIL - # * I/O bound task (which releases GIL, of course) - # - # We use cpu_count + 4 for both types of tasks. - # But we limit it to 32 to avoid consuming surprisingly large resource - # on many core machine. - max_workers = min(32, (os.cpu_count() or 1) + 4) - if max_workers <= 0: - raise ValueError("max_workers must be greater than 0") - - if initializer is not None and not callable(initializer): - raise TypeError("initializer must be a callable") - - self._max_workers = max_workers - self._work_queue = queue.SimpleQueue() - self._idle_semaphore = threading.Semaphore(0) - self._threads = set() - self._broken = False - self._shutdown = False - self._shutdown_lock = threading.Lock() - self._thread_name_prefix = (thread_name_prefix or - ("ThreadPoolExecutor-%d" % self._counter())) - self._initializer = initializer - self._initargs = initargs - - def submit(*args, **kwargs): - if len(args) >= 2: - self, fn, *args = args - elif not args: - raise TypeError("descriptor 'submit' of 'ThreadPoolExecutor' object " - "needs an argument") - elif 'fn' in kwargs: - fn = kwargs.pop('fn') - self, *args = args - import warnings - warnings.warn("Passing 'fn' as keyword argument is deprecated", - DeprecationWarning, stacklevel=2) - else: - raise TypeError('submit expected at least 1 positional argument, ' - 'got %d' % (len(args)-1)) - - with self._shutdown_lock: - if self._broken: - raise BrokenThreadPool(self._broken) - - if self._shutdown: - raise RuntimeError('cannot schedule new futures after shutdown') - if _shutdown: - raise RuntimeError('cannot schedule new futures after ' - 'interpreter shutdown') - - f = _base.Future() - w = _WorkItem(f, fn, args, kwargs) - - self._work_queue.put(w) - self._adjust_thread_count() - return f - submit.__text_signature__ = _base.Executor.submit.__text_signature__ - submit.__doc__ = _base.Executor.submit.__doc__ - - def _adjust_thread_count(self): - # if idle threads are available, don't spin new threads - if self._idle_semaphore.acquire(timeout=0): - return - - # When the executor gets lost, the weakref callback will wake up - # the worker threads. - def weakref_cb(_, q=self._work_queue): - q.put(None) - - num_threads = len(self._threads) - if num_threads < self._max_workers: - thread_name = '%s_%d' % (self._thread_name_prefix or self, - num_threads) - t = threading.Thread(name=thread_name, target=_worker, - args=(weakref.ref(self, weakref_cb), - self._work_queue, - self._initializer, - self._initargs)) - t.daemon = True - t.start() - self._threads.add(t) - _threads_queues[t] = self._work_queue - - def _initializer_failed(self): - with self._shutdown_lock: - self._broken = ('A thread initializer failed, the thread pool ' - 'is not usable anymore') - # Drain work queue and mark pending futures failed - while True: - try: - work_item = self._work_queue.get_nowait() - except queue.Empty: - break - if work_item is not None: - work_item.future.set_exception(BrokenThreadPool(self._broken)) - - def shutdown(self, wait=True): - with self._shutdown_lock: - self._shutdown = True - self._work_queue.put(None) - if wait: - for t in self._threads: - t.join() - shutdown.__doc__ = _base.Executor.shutdown.__doc__ diff --git a/dist/lib/configparser.py b/dist/lib/configparser.py deleted file mode 100644 index 924cc56..0000000 --- a/dist/lib/configparser.py +++ /dev/null @@ -1,1363 +0,0 @@ -"""Configuration file parser. - -A configuration file consists of sections, lead by a "[section]" header, -and followed by "name: value" entries, with continuations and such in -the style of RFC 822. - -Intrinsic defaults can be specified by passing them into the -ConfigParser constructor as a dictionary. - -class: - -ConfigParser -- responsible for parsing a list of - configuration files, and managing the parsed database. - - methods: - - __init__(defaults=None, dict_type=_default_dict, allow_no_value=False, - delimiters=('=', ':'), comment_prefixes=('#', ';'), - inline_comment_prefixes=None, strict=True, - empty_lines_in_values=True, default_section='DEFAULT', - interpolation=, converters=): - Create the parser. When `defaults' is given, it is initialized into the - dictionary or intrinsic defaults. The keys must be strings, the values - must be appropriate for %()s string interpolation. - - When `dict_type' is given, it will be used to create the dictionary - objects for the list of sections, for the options within a section, and - for the default values. - - When `delimiters' is given, it will be used as the set of substrings - that divide keys from values. - - When `comment_prefixes' is given, it will be used as the set of - substrings that prefix comments in empty lines. Comments can be - indented. - - When `inline_comment_prefixes' is given, it will be used as the set of - substrings that prefix comments in non-empty lines. - - When `strict` is True, the parser won't allow for any section or option - duplicates while reading from a single source (file, string or - dictionary). Default is True. - - When `empty_lines_in_values' is False (default: True), each empty line - marks the end of an option. Otherwise, internal empty lines of - a multiline option are kept as part of the value. - - When `allow_no_value' is True (default: False), options without - values are accepted; the value presented for these is None. - - When `default_section' is given, the name of the special section is - named accordingly. By default it is called ``"DEFAULT"`` but this can - be customized to point to any other valid section name. Its current - value can be retrieved using the ``parser_instance.default_section`` - attribute and may be modified at runtime. - - When `interpolation` is given, it should be an Interpolation subclass - instance. It will be used as the handler for option value - pre-processing when using getters. RawConfigParser objects don't do - any sort of interpolation, whereas ConfigParser uses an instance of - BasicInterpolation. The library also provides a ``zc.buildbot`` - inspired ExtendedInterpolation implementation. - - When `converters` is given, it should be a dictionary where each key - represents the name of a type converter and each value is a callable - implementing the conversion from string to the desired datatype. Every - converter gets its corresponding get*() method on the parser object and - section proxies. - - sections() - Return all the configuration section names, sans DEFAULT. - - has_section(section) - Return whether the given section exists. - - has_option(section, option) - Return whether the given option exists in the given section. - - options(section) - Return list of configuration options for the named section. - - read(filenames, encoding=None) - Read and parse the iterable of named configuration files, given by - name. A single filename is also allowed. Non-existing files - are ignored. Return list of successfully read files. - - read_file(f, filename=None) - Read and parse one configuration file, given as a file object. - The filename defaults to f.name; it is only used in error - messages (if f has no `name' attribute, the string `' is used). - - read_string(string) - Read configuration from a given string. - - read_dict(dictionary) - Read configuration from a dictionary. Keys are section names, - values are dictionaries with keys and values that should be present - in the section. If the used dictionary type preserves order, sections - and their keys will be added in order. Values are automatically - converted to strings. - - get(section, option, raw=False, vars=None, fallback=_UNSET) - Return a string value for the named option. All % interpolations are - expanded in the return values, based on the defaults passed into the - constructor and the DEFAULT section. Additional substitutions may be - provided using the `vars' argument, which must be a dictionary whose - contents override any pre-existing defaults. If `option' is a key in - `vars', the value from `vars' is used. - - getint(section, options, raw=False, vars=None, fallback=_UNSET) - Like get(), but convert value to an integer. - - getfloat(section, options, raw=False, vars=None, fallback=_UNSET) - Like get(), but convert value to a float. - - getboolean(section, options, raw=False, vars=None, fallback=_UNSET) - Like get(), but convert value to a boolean (currently case - insensitively defined as 0, false, no, off for False, and 1, true, - yes, on for True). Returns False or True. - - items(section=_UNSET, raw=False, vars=None) - If section is given, return a list of tuples with (name, value) for - each option in the section. Otherwise, return a list of tuples with - (section_name, section_proxy) for each section, including DEFAULTSECT. - - remove_section(section) - Remove the given file section and all its options. - - remove_option(section, option) - Remove the given option from the given section. - - set(section, option, value) - Set the given option. - - write(fp, space_around_delimiters=True) - Write the configuration state in .ini format. If - `space_around_delimiters' is True (the default), delimiters - between keys and values are surrounded by spaces. -""" - -from collections.abc import MutableMapping -from collections import ChainMap as _ChainMap -import functools -import io -import itertools -import os -import re -import sys -import warnings - -__all__ = ["NoSectionError", "DuplicateOptionError", "DuplicateSectionError", - "NoOptionError", "InterpolationError", "InterpolationDepthError", - "InterpolationMissingOptionError", "InterpolationSyntaxError", - "ParsingError", "MissingSectionHeaderError", - "ConfigParser", "SafeConfigParser", "RawConfigParser", - "Interpolation", "BasicInterpolation", "ExtendedInterpolation", - "LegacyInterpolation", "SectionProxy", "ConverterMapping", - "DEFAULTSECT", "MAX_INTERPOLATION_DEPTH"] - -_default_dict = dict -DEFAULTSECT = "DEFAULT" - -MAX_INTERPOLATION_DEPTH = 10 - - - -# exception classes -class Error(Exception): - """Base class for ConfigParser exceptions.""" - - def __init__(self, msg=''): - self.message = msg - Exception.__init__(self, msg) - - def __repr__(self): - return self.message - - __str__ = __repr__ - - -class NoSectionError(Error): - """Raised when no section matches a requested option.""" - - def __init__(self, section): - Error.__init__(self, 'No section: %r' % (section,)) - self.section = section - self.args = (section, ) - - -class DuplicateSectionError(Error): - """Raised when a section is repeated in an input source. - - Possible repetitions that raise this exception are: multiple creation - using the API or in strict parsers when a section is found more than once - in a single input file, string or dictionary. - """ - - def __init__(self, section, source=None, lineno=None): - msg = [repr(section), " already exists"] - if source is not None: - message = ["While reading from ", repr(source)] - if lineno is not None: - message.append(" [line {0:2d}]".format(lineno)) - message.append(": section ") - message.extend(msg) - msg = message - else: - msg.insert(0, "Section ") - Error.__init__(self, "".join(msg)) - self.section = section - self.source = source - self.lineno = lineno - self.args = (section, source, lineno) - - -class DuplicateOptionError(Error): - """Raised by strict parsers when an option is repeated in an input source. - - Current implementation raises this exception only when an option is found - more than once in a single file, string or dictionary. - """ - - def __init__(self, section, option, source=None, lineno=None): - msg = [repr(option), " in section ", repr(section), - " already exists"] - if source is not None: - message = ["While reading from ", repr(source)] - if lineno is not None: - message.append(" [line {0:2d}]".format(lineno)) - message.append(": option ") - message.extend(msg) - msg = message - else: - msg.insert(0, "Option ") - Error.__init__(self, "".join(msg)) - self.section = section - self.option = option - self.source = source - self.lineno = lineno - self.args = (section, option, source, lineno) - - -class NoOptionError(Error): - """A requested option was not found.""" - - def __init__(self, option, section): - Error.__init__(self, "No option %r in section: %r" % - (option, section)) - self.option = option - self.section = section - self.args = (option, section) - - -class InterpolationError(Error): - """Base class for interpolation-related exceptions.""" - - def __init__(self, option, section, msg): - Error.__init__(self, msg) - self.option = option - self.section = section - self.args = (option, section, msg) - - -class InterpolationMissingOptionError(InterpolationError): - """A string substitution required a setting which was not available.""" - - def __init__(self, option, section, rawval, reference): - msg = ("Bad value substitution: option {!r} in section {!r} contains " - "an interpolation key {!r} which is not a valid option name. " - "Raw value: {!r}".format(option, section, reference, rawval)) - InterpolationError.__init__(self, option, section, msg) - self.reference = reference - self.args = (option, section, rawval, reference) - - -class InterpolationSyntaxError(InterpolationError): - """Raised when the source text contains invalid syntax. - - Current implementation raises this exception when the source text into - which substitutions are made does not conform to the required syntax. - """ - - -class InterpolationDepthError(InterpolationError): - """Raised when substitutions are nested too deeply.""" - - def __init__(self, option, section, rawval): - msg = ("Recursion limit exceeded in value substitution: option {!r} " - "in section {!r} contains an interpolation key which " - "cannot be substituted in {} steps. Raw value: {!r}" - "".format(option, section, MAX_INTERPOLATION_DEPTH, - rawval)) - InterpolationError.__init__(self, option, section, msg) - self.args = (option, section, rawval) - - -class ParsingError(Error): - """Raised when a configuration file does not follow legal syntax.""" - - def __init__(self, source=None, filename=None): - # Exactly one of `source'/`filename' arguments has to be given. - # `filename' kept for compatibility. - if filename and source: - raise ValueError("Cannot specify both `filename' and `source'. " - "Use `source'.") - elif not filename and not source: - raise ValueError("Required argument `source' not given.") - elif filename: - source = filename - Error.__init__(self, 'Source contains parsing errors: %r' % source) - self.source = source - self.errors = [] - self.args = (source, ) - - @property - def filename(self): - """Deprecated, use `source'.""" - warnings.warn( - "The 'filename' attribute will be removed in future versions. " - "Use 'source' instead.", - DeprecationWarning, stacklevel=2 - ) - return self.source - - @filename.setter - def filename(self, value): - """Deprecated, user `source'.""" - warnings.warn( - "The 'filename' attribute will be removed in future versions. " - "Use 'source' instead.", - DeprecationWarning, stacklevel=2 - ) - self.source = value - - def append(self, lineno, line): - self.errors.append((lineno, line)) - self.message += '\n\t[line %2d]: %s' % (lineno, line) - - -class MissingSectionHeaderError(ParsingError): - """Raised when a key-value pair is found before any section header.""" - - def __init__(self, filename, lineno, line): - Error.__init__( - self, - 'File contains no section headers.\nfile: %r, line: %d\n%r' % - (filename, lineno, line)) - self.source = filename - self.lineno = lineno - self.line = line - self.args = (filename, lineno, line) - - -# Used in parser getters to indicate the default behaviour when a specific -# option is not found it to raise an exception. Created to enable `None' as -# a valid fallback value. -_UNSET = object() - - -class Interpolation: - """Dummy interpolation that passes the value through with no changes.""" - - def before_get(self, parser, section, option, value, defaults): - return value - - def before_set(self, parser, section, option, value): - return value - - def before_read(self, parser, section, option, value): - return value - - def before_write(self, parser, section, option, value): - return value - - -class BasicInterpolation(Interpolation): - """Interpolation as implemented in the classic ConfigParser. - - The option values can contain format strings which refer to other values in - the same section, or values in the special default section. - - For example: - - something: %(dir)s/whatever - - would resolve the "%(dir)s" to the value of dir. All reference - expansions are done late, on demand. If a user needs to use a bare % in - a configuration file, she can escape it by writing %%. Other % usage - is considered a user error and raises `InterpolationSyntaxError'.""" - - _KEYCRE = re.compile(r"%\(([^)]+)\)s") - - def before_get(self, parser, section, option, value, defaults): - L = [] - self._interpolate_some(parser, option, L, value, section, defaults, 1) - return ''.join(L) - - def before_set(self, parser, section, option, value): - tmp_value = value.replace('%%', '') # escaped percent signs - tmp_value = self._KEYCRE.sub('', tmp_value) # valid syntax - if '%' in tmp_value: - raise ValueError("invalid interpolation syntax in %r at " - "position %d" % (value, tmp_value.find('%'))) - return value - - def _interpolate_some(self, parser, option, accum, rest, section, map, - depth): - rawval = parser.get(section, option, raw=True, fallback=rest) - if depth > MAX_INTERPOLATION_DEPTH: - raise InterpolationDepthError(option, section, rawval) - while rest: - p = rest.find("%") - if p < 0: - accum.append(rest) - return - if p > 0: - accum.append(rest[:p]) - rest = rest[p:] - # p is no longer used - c = rest[1:2] - if c == "%": - accum.append("%") - rest = rest[2:] - elif c == "(": - m = self._KEYCRE.match(rest) - if m is None: - raise InterpolationSyntaxError(option, section, - "bad interpolation variable reference %r" % rest) - var = parser.optionxform(m.group(1)) - rest = rest[m.end():] - try: - v = map[var] - except KeyError: - raise InterpolationMissingOptionError( - option, section, rawval, var) from None - if "%" in v: - self._interpolate_some(parser, option, accum, v, - section, map, depth + 1) - else: - accum.append(v) - else: - raise InterpolationSyntaxError( - option, section, - "'%%' must be followed by '%%' or '(', " - "found: %r" % (rest,)) - - -class ExtendedInterpolation(Interpolation): - """Advanced variant of interpolation, supports the syntax used by - `zc.buildout'. Enables interpolation between sections.""" - - _KEYCRE = re.compile(r"\$\{([^}]+)\}") - - def before_get(self, parser, section, option, value, defaults): - L = [] - self._interpolate_some(parser, option, L, value, section, defaults, 1) - return ''.join(L) - - def before_set(self, parser, section, option, value): - tmp_value = value.replace('$$', '') # escaped dollar signs - tmp_value = self._KEYCRE.sub('', tmp_value) # valid syntax - if '$' in tmp_value: - raise ValueError("invalid interpolation syntax in %r at " - "position %d" % (value, tmp_value.find('$'))) - return value - - def _interpolate_some(self, parser, option, accum, rest, section, map, - depth): - rawval = parser.get(section, option, raw=True, fallback=rest) - if depth > MAX_INTERPOLATION_DEPTH: - raise InterpolationDepthError(option, section, rawval) - while rest: - p = rest.find("$") - if p < 0: - accum.append(rest) - return - if p > 0: - accum.append(rest[:p]) - rest = rest[p:] - # p is no longer used - c = rest[1:2] - if c == "$": - accum.append("$") - rest = rest[2:] - elif c == "{": - m = self._KEYCRE.match(rest) - if m is None: - raise InterpolationSyntaxError(option, section, - "bad interpolation variable reference %r" % rest) - path = m.group(1).split(':') - rest = rest[m.end():] - sect = section - opt = option - try: - if len(path) == 1: - opt = parser.optionxform(path[0]) - v = map[opt] - elif len(path) == 2: - sect = path[0] - opt = parser.optionxform(path[1]) - v = parser.get(sect, opt, raw=True) - else: - raise InterpolationSyntaxError( - option, section, - "More than one ':' found: %r" % (rest,)) - except (KeyError, NoSectionError, NoOptionError): - raise InterpolationMissingOptionError( - option, section, rawval, ":".join(path)) from None - if "$" in v: - self._interpolate_some(parser, opt, accum, v, sect, - dict(parser.items(sect, raw=True)), - depth + 1) - else: - accum.append(v) - else: - raise InterpolationSyntaxError( - option, section, - "'$' must be followed by '$' or '{', " - "found: %r" % (rest,)) - - -class LegacyInterpolation(Interpolation): - """Deprecated interpolation used in old versions of ConfigParser. - Use BasicInterpolation or ExtendedInterpolation instead.""" - - _KEYCRE = re.compile(r"%\(([^)]*)\)s|.") - - def before_get(self, parser, section, option, value, vars): - rawval = value - depth = MAX_INTERPOLATION_DEPTH - while depth: # Loop through this until it's done - depth -= 1 - if value and "%(" in value: - replace = functools.partial(self._interpolation_replace, - parser=parser) - value = self._KEYCRE.sub(replace, value) - try: - value = value % vars - except KeyError as e: - raise InterpolationMissingOptionError( - option, section, rawval, e.args[0]) from None - else: - break - if value and "%(" in value: - raise InterpolationDepthError(option, section, rawval) - return value - - def before_set(self, parser, section, option, value): - return value - - @staticmethod - def _interpolation_replace(match, parser): - s = match.group(1) - if s is None: - return match.group() - else: - return "%%(%s)s" % parser.optionxform(s) - - -class RawConfigParser(MutableMapping): - """ConfigParser that does not do interpolation.""" - - # Regular expressions for parsing section headers and options - _SECT_TMPL = r""" - \[ # [ - (?P
[^]]+) # very permissive! - \] # ] - """ - _OPT_TMPL = r""" - (?P