mirror of
https://github.com/imayushsaini/Bombsquad-Ballistica-Modded-Server.git
synced 2026-08-15 13:04:30 +00:00
syncing 1.7.60 ballistica binary
This commit is contained in:
parent
2870c850c4
commit
e02f1dbe52
241 changed files with 10185 additions and 1827 deletions
63
dist/ba_data/python/babase/__init__.py
vendored
63
dist/ba_data/python/babase/__init__.py
vendored
|
|
@ -7,6 +7,7 @@ directly. Instead one should use purpose-built packages such as
|
|||
:mod:`bascenev1` or :mod:`bauiv1` which themselves import various
|
||||
functionality from here and reexpose it in a more focused way.
|
||||
"""
|
||||
|
||||
# pylint: disable=redefined-builtin
|
||||
|
||||
# ba_meta require api 9
|
||||
|
|
@ -15,7 +16,8 @@ functionality from here and reexpose it in a more focused way.
|
|||
# from other modules/packages. Code *within* this package should import
|
||||
# things from this package's submodules directly to reduce the chance of
|
||||
# dependency loops. The exception is TYPE_CHECKING blocks and
|
||||
# annotations since those aren't evaluated at runtime.
|
||||
# annotations - since those aren't evaluated at runtime, it is cleaner
|
||||
# looking to use top level names directly.
|
||||
|
||||
import _babase
|
||||
from _babase import (
|
||||
|
|
@ -125,6 +127,7 @@ from _babase import (
|
|||
)
|
||||
|
||||
from babase._accountv2 import AccountV2Handle, AccountV2Subsystem
|
||||
from babase._analytics import AnalyticsSubsystem
|
||||
from babase._app import App, AppState
|
||||
from babase._appcomponent import AppComponentSubsystem
|
||||
from babase._appconfig import commit_app_config
|
||||
|
|
@ -134,68 +137,71 @@ from babase._appsubsystem import AppSubsystem
|
|||
from babase._appmodeselector import AppModeSelector
|
||||
from babase._appconfig import AppConfig
|
||||
from babase._apputils import (
|
||||
AppHealthSubsystem,
|
||||
get_remote_app_name,
|
||||
handle_leftover_v1_cloud_log_file,
|
||||
is_browser_likely_available,
|
||||
get_remote_app_name,
|
||||
AppHealthSubsystem,
|
||||
utc_now_cloud,
|
||||
)
|
||||
from babase._cloud import CloudSubscription
|
||||
from babase._devconsole import (
|
||||
DevConsoleButtonDef,
|
||||
DevConsoleSubsystem,
|
||||
DevConsoleTab,
|
||||
DevConsoleTabEntry,
|
||||
DevConsoleSubsystem,
|
||||
)
|
||||
from babase._discord import DiscordSubsystem
|
||||
from babase._emptyappmode import EmptyAppMode
|
||||
from babase._error import (
|
||||
ActivityNotFoundError,
|
||||
ActorNotFoundError,
|
||||
ContextError,
|
||||
DelegateNotFoundError,
|
||||
InputDeviceNotFoundError,
|
||||
MapNotFoundError,
|
||||
NodeNotFoundError,
|
||||
NotFoundError,
|
||||
PlayerNotFoundError,
|
||||
SessionPlayerNotFoundError,
|
||||
NodeNotFoundError,
|
||||
ActorNotFoundError,
|
||||
InputDeviceNotFoundError,
|
||||
WidgetNotFoundError,
|
||||
ActivityNotFoundError,
|
||||
TeamNotFoundError,
|
||||
MapNotFoundError,
|
||||
SessionTeamNotFoundError,
|
||||
SessionNotFoundError,
|
||||
DelegateNotFoundError,
|
||||
SessionPlayerNotFoundError,
|
||||
SessionTeamNotFoundError,
|
||||
TeamNotFoundError,
|
||||
WidgetNotFoundError,
|
||||
)
|
||||
from babase._gc import GarbageCollectionSubsystem
|
||||
from babase._general import (
|
||||
DisplayTime,
|
||||
AppTime,
|
||||
WeakCall,
|
||||
Call,
|
||||
existing,
|
||||
CallPartial,
|
||||
CallStrict,
|
||||
DisplayTime,
|
||||
Existable,
|
||||
verify_object_death,
|
||||
storagename,
|
||||
getclass,
|
||||
WeakCall,
|
||||
WeakCallPartial,
|
||||
WeakCallStrict,
|
||||
existing,
|
||||
get_type_name,
|
||||
getclass,
|
||||
storagename,
|
||||
verify_object_death,
|
||||
)
|
||||
from babase._language import Lstr, LanguageSubsystem
|
||||
from babase._language import LanguageSubsystem, Lstr
|
||||
from babase._locale import LocaleSubsystem
|
||||
from babase._logging import (
|
||||
balog,
|
||||
accountlog,
|
||||
applog,
|
||||
balog,
|
||||
lifecyclelog,
|
||||
netlog,
|
||||
uilog,
|
||||
)
|
||||
from babase._login import LoginAdapter, LoginInfo
|
||||
|
||||
from babase._mgen.enums import (
|
||||
Permission,
|
||||
SpecialChar,
|
||||
InputType,
|
||||
UIScale,
|
||||
Permission,
|
||||
QuitType,
|
||||
SpecialChar,
|
||||
UIScale,
|
||||
)
|
||||
from babase._math import normalized_color, is_point_in_box, vec3validate
|
||||
from babase._meta import MetadataSubsystem
|
||||
|
|
@ -216,6 +222,7 @@ __all__ = [
|
|||
'ActorNotFoundError',
|
||||
'allows_ticket_sales',
|
||||
'add_clean_frame_callback',
|
||||
'AnalyticsSubsystem',
|
||||
'android_get_external_files_dir',
|
||||
'app',
|
||||
'App',
|
||||
|
|
@ -242,6 +249,8 @@ __all__ = [
|
|||
'atexit',
|
||||
'balog',
|
||||
'Call',
|
||||
'CallPartial',
|
||||
'CallStrict',
|
||||
'fullscreen_control_available',
|
||||
'fullscreen_control_get',
|
||||
'fullscreen_control_key_shortcut',
|
||||
|
|
@ -391,6 +400,8 @@ __all__ = [
|
|||
'vec3validate',
|
||||
'verify_object_death',
|
||||
'WeakCall',
|
||||
'WeakCallPartial',
|
||||
'WeakCallStrict',
|
||||
'WidgetNotFoundError',
|
||||
'workspaces_in_use',
|
||||
'WorkspaceSubsystem',
|
||||
|
|
|
|||
73
dist/ba_data/python/babase/_analytics.py
vendored
Normal file
73
dist/ba_data/python/babase/_analytics.py
vendored
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Analytics functionality."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import bacommon.cloud
|
||||
import _babase
|
||||
|
||||
from babase._logging import balog
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bacommon.analytics import AnalyticsEvent
|
||||
|
||||
|
||||
class AnalyticsSubsystem:
|
||||
"""Subsystem for wrangling analytics.
|
||||
|
||||
Access the single shared instance of this class via the
|
||||
:attr:`~babase.App.analytics` attr on the :class:`~babase.App`
|
||||
class.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.enabled: bool = True
|
||||
|
||||
def submit_event(self, event: AnalyticsEvent) -> None:
|
||||
"""Submit an event.
|
||||
|
||||
Should only be called from the logic thread.
|
||||
"""
|
||||
|
||||
if not _babase.in_logic_thread():
|
||||
balog.error(
|
||||
'submit_event() called outside logic thread.', stack_info=True
|
||||
)
|
||||
return
|
||||
|
||||
# No-op if analytics are disabled or we don't have plus.
|
||||
if not self.enabled:
|
||||
return
|
||||
|
||||
plus = _babase.app.plus
|
||||
if plus is None:
|
||||
return
|
||||
|
||||
# Currently just no-op if it seems we're not connected. Perhaps
|
||||
# in the future we'd want to save these and submit later when we
|
||||
# are.
|
||||
if not plus.cloud.is_connected():
|
||||
return
|
||||
|
||||
# Just kick off an immediate send in the bg with or without
|
||||
# account info.
|
||||
account = plus.accounts.primary
|
||||
if account is None:
|
||||
plus.cloud.send_message_cb(
|
||||
bacommon.cloud.AnalyticsEventMessage(event),
|
||||
on_response=self._on_analytics_message_response,
|
||||
)
|
||||
else:
|
||||
with account:
|
||||
plus.cloud.send_message_cb(
|
||||
bacommon.cloud.AnalyticsEventMessage(event),
|
||||
on_response=self._on_analytics_message_response,
|
||||
)
|
||||
|
||||
def _on_analytics_message_response(
|
||||
self, response: Exception | None
|
||||
) -> None:
|
||||
pass
|
||||
21
dist/ba_data/python/babase/_app.py
vendored
21
dist/ba_data/python/babase/_app.py
vendored
|
|
@ -2,10 +2,12 @@
|
|||
#
|
||||
# pylint: disable=too-many-lines
|
||||
"""Functionality related to the high level state of the app."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import time
|
||||
import asyncio
|
||||
import logging
|
||||
from enum import Enum
|
||||
from functools import partial
|
||||
|
|
@ -28,12 +30,12 @@ from babase._appmodeselector import AppModeSelector
|
|||
from babase._appintent import AppIntentDefault, AppIntentExec
|
||||
from babase._stringedit import StringEditSubsystem
|
||||
from babase._devconsole import DevConsoleSubsystem
|
||||
from babase._analytics import AnalyticsSubsystem
|
||||
from babase._appconfig import AppConfig
|
||||
from babase._logging import lifecyclelog, applog
|
||||
from babase._gc import GarbageCollectionSubsystem
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import asyncio
|
||||
from typing import Any, Callable, Coroutine, Generator, Awaitable
|
||||
from concurrent.futures import Future
|
||||
|
||||
|
|
@ -152,6 +154,9 @@ class App:
|
|||
#: Subsystem for wrangling the dev-console UI.
|
||||
self.devconsole: DevConsoleSubsystem = DevConsoleSubsystem()
|
||||
|
||||
#: Subsystem for wrangling analytics.
|
||||
self.analytics: AnalyticsSubsystem = AnalyticsSubsystem()
|
||||
|
||||
#: Incremented each time the app leaves the
|
||||
#: :attr:`~babase.AppState.SUSPENDED` state. This can be a simple
|
||||
#: way to determine if network data should be refreshed/etc.
|
||||
|
|
@ -281,8 +286,9 @@ class App:
|
|||
def mode_selector(self, selector: babase.AppModeSelector) -> None:
|
||||
self._mode_selector = selector
|
||||
|
||||
def _on_task_done(self, task: asyncio.Task) -> None:
|
||||
def _on_task_done(self, task: Any) -> None:
|
||||
# Report any errors that occurred.
|
||||
assert isinstance(task, asyncio.Task)
|
||||
try:
|
||||
exc = task.exception()
|
||||
if exc is not None:
|
||||
|
|
@ -1028,7 +1034,6 @@ class App:
|
|||
)
|
||||
|
||||
async def _shutdown(self) -> None:
|
||||
import asyncio
|
||||
|
||||
_babase.lock_all_input()
|
||||
try:
|
||||
|
|
@ -1054,13 +1059,16 @@ class App:
|
|||
self, coro: Coroutine[None, None, None]
|
||||
) -> None:
|
||||
"""Run a shutdown task; report errors and abort if taking too long."""
|
||||
import asyncio
|
||||
|
||||
task = asyncio.create_task(coro)
|
||||
try:
|
||||
await asyncio.wait_for(task, self.SHUTDOWN_TASK_TIMEOUT_SECONDS)
|
||||
except TimeoutError:
|
||||
# Log simple error message if it times out.
|
||||
logging.error('Timed out waiting for shutdown task %s.', coro)
|
||||
except Exception:
|
||||
logging.exception('Error in shutdown task (%s).', coro)
|
||||
# Go with full ugly stack trace for anything unexpected.
|
||||
logging.exception('Error in shutdown task %s.', coro)
|
||||
|
||||
def _on_suspend(self) -> None:
|
||||
"""Called when the app goes to a suspended state."""
|
||||
|
|
@ -1136,7 +1144,6 @@ class App:
|
|||
)
|
||||
|
||||
async def _wait_for_shutdown_suppressions(self) -> None:
|
||||
import asyncio
|
||||
|
||||
# Spin and wait for anything blocking shutdown to complete.
|
||||
starttime = _babase.apptime()
|
||||
|
|
@ -1153,7 +1160,6 @@ class App:
|
|||
)
|
||||
|
||||
async def _fade_and_shutdown_graphics(self) -> None:
|
||||
import asyncio
|
||||
|
||||
# Kick off a short fade and give it time to complete.
|
||||
lifecyclelog.info('fade-and-shutdown-graphics begin')
|
||||
|
|
@ -1189,7 +1195,6 @@ class App:
|
|||
lifecyclelog.info('fade-and-shutdown-graphics end')
|
||||
|
||||
async def _fade_and_shutdown_audio(self) -> None:
|
||||
import asyncio
|
||||
|
||||
# Tell the audio system to go down and give it a bit of
|
||||
# time to do so gracefully.
|
||||
|
|
|
|||
1
dist/ba_data/python/babase/_appcomponent.py
vendored
1
dist/ba_data/python/babase/_appcomponent.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Provides the AppComponent class."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, cast
|
||||
|
|
|
|||
1
dist/ba_data/python/babase/_appconfig.py
vendored
1
dist/ba_data/python/babase/_appconfig.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Provides the AppConfig class."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
|
|
|||
1
dist/ba_data/python/babase/_appintent.py
vendored
1
dist/ba_data/python/babase/_appintent.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Provides AppIntent functionality."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
|
|
|||
1
dist/ba_data/python/babase/_appmode.py
vendored
1
dist/ba_data/python/babase/_appmode.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Provides AppMode functionality."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Contains AppModeSelector base class."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
|
|
|||
2
dist/ba_data/python/babase/_appsubsystem.py
vendored
2
dist/ba_data/python/babase/_appsubsystem.py
vendored
|
|
@ -1,11 +1,11 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Provides the AppSubsystem base class."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from babase import UIScale
|
||||
|
||||
|
|
|
|||
1
dist/ba_data/python/babase/_apputils.py
vendored
1
dist/ba_data/python/babase/_apputils.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Utility functionality related to the overall operation of the app."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
|
|
|||
1
dist/ba_data/python/babase/_cloud.py
vendored
1
dist/ba_data/python/babase/_cloud.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Cloud related functionality."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
|
|
|||
1
dist/ba_data/python/babase/_devconsole.py
vendored
1
dist/ba_data/python/babase/_devconsole.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Dev-Console functionality."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Predefined tabs for the dev console."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
|
|
|
|||
1
dist/ba_data/python/babase/_discord.py
vendored
1
dist/ba_data/python/babase/_discord.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
|
||||
"""Functionality related to discord sdk integration"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, override
|
||||
|
|
|
|||
3
dist/ba_data/python/babase/_emptyappmode.py
vendored
3
dist/ba_data/python/babase/_emptyappmode.py
vendored
|
|
@ -1,12 +1,11 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Provides AppMode functionality."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, override
|
||||
|
||||
# from bacommon.app import AppExperience
|
||||
|
||||
import _babase
|
||||
from babase._appmode import AppMode
|
||||
from babase._appintent import AppIntentExec, AppIntentDefault
|
||||
|
|
|
|||
25
dist/ba_data/python/babase/_env.py
vendored
25
dist/ba_data/python/babase/_env.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Environment related functionality."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
|
@ -158,26 +159,6 @@ def on_main_thread_start_app() -> None:
|
|||
# situations.
|
||||
__main__.__builtins__.help = _CustomHelper()
|
||||
|
||||
# UPDATE: As of May 2025 I'm no longer seeing the below issue, so
|
||||
# disabling this workaround for now and will remove it soon if no
|
||||
# issues arise.
|
||||
|
||||
# On Windows I'm seeing the following error creating asyncio loops
|
||||
# in background threads with the default proactor setup:
|
||||
|
||||
# ValueError: set_wakeup_fd only works in main thread of the main
|
||||
# interpreter.
|
||||
|
||||
# So let's explicitly request selector loops. Interestingly this
|
||||
# error only started showing up once I moved Python init to the main
|
||||
# thread; previously the various asyncio bg thread loops were
|
||||
# working fine (maybe something caused them to default to selector
|
||||
# in that case?..
|
||||
if sys.platform == 'win32' and bool(False):
|
||||
import asyncio
|
||||
|
||||
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
||||
|
||||
# Kick off networking bootstrapping. We do this here instead of in
|
||||
# our app net-subsystem so that it can proceed in parallel with the
|
||||
# rest of our bootstrapping (as networking stuff is often an overall
|
||||
|
|
@ -647,5 +628,5 @@ class _CustomHelper:
|
|||
'Interactive help is not available in this environment.\n'
|
||||
'Type help(object) for help about object.'
|
||||
)
|
||||
return None
|
||||
return pydoc.help(*args, **kwds)
|
||||
return
|
||||
pydoc.help(*args, **kwds)
|
||||
|
|
|
|||
1
dist/ba_data/python/babase/_gc.py
vendored
1
dist/ba_data/python/babase/_gc.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Utility functionality related to the overall operation of the app."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import gc
|
||||
|
|
|
|||
327
dist/ba_data/python/babase/_general.py
vendored
327
dist/ba_data/python/babase/_general.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Utility snippets applying to generic Python code."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
|
@ -9,6 +10,7 @@ import weakref
|
|||
import random
|
||||
import logging
|
||||
import inspect
|
||||
import warnings
|
||||
from typing import TYPE_CHECKING, TypeVar, Protocol, NewType, override
|
||||
|
||||
from efro.terminal import Clr
|
||||
|
|
@ -17,7 +19,7 @@ import _babase
|
|||
|
||||
if TYPE_CHECKING:
|
||||
import functools
|
||||
from typing import Any
|
||||
from typing import Any, Callable
|
||||
|
||||
|
||||
# Declare distinct types for different time measurements we use so the
|
||||
|
|
@ -93,7 +95,22 @@ def get_type_name(cls: type) -> str:
|
|||
return f'{cls.__module__}.{cls.__qualname__}'
|
||||
|
||||
|
||||
class _WeakCall:
|
||||
# Note: Something here is wonky with pylint, possibly related to our
|
||||
# custom pylint plugin. Disabling all checks seems to fix it.
|
||||
# pylint: disable=all
|
||||
if TYPE_CHECKING:
|
||||
# For type-checking, we point WeakCall and Call at
|
||||
# functools.partial. This gives decent type-checking considering the
|
||||
# open-ended nature of these calls (args being supplied at create
|
||||
# time and/or at call time). Just remember that we're slightly lying
|
||||
# to the type-checker here.
|
||||
WeakCallPartial = functools.partial
|
||||
CallPartial = functools.partial
|
||||
WeakCall = functools.partial
|
||||
Call = functools.partial
|
||||
else:
|
||||
|
||||
class WeakCallPartial:
|
||||
"""Wrap a callable and arguments into a single callable object.
|
||||
|
||||
When passed a bound method as the callable, the instance portion of
|
||||
|
|
@ -143,50 +160,59 @@ class _WeakCall:
|
|||
|
||||
_did_invalid_call_warning = False
|
||||
|
||||
def __init__(self, *args: Any, **keywds: Any) -> None:
|
||||
if hasattr(args[0], '__func__'):
|
||||
self._call = WeakMethod(args[0])
|
||||
def __init__(self, call: Any, /, *args: Any, **keywds: Any) -> None:
|
||||
# Note: keeping _call, _args, _keywds private in this case
|
||||
# since we sub functools.partial for ourself in
|
||||
# type-checking so they will be unrecognized anyway. Use
|
||||
# non-partial versions if you want to access those.
|
||||
if hasattr(call, '__func__'):
|
||||
self._call = WeakMethod(call)
|
||||
else:
|
||||
app = _babase.app
|
||||
if not self._did_invalid_call_warning:
|
||||
logging.warning(
|
||||
'Warning: callable passed to babase.WeakCall() is not'
|
||||
' weak-referencable (%s); use functools.partial instead'
|
||||
'Warning: callable passed to WeakCall() is not'
|
||||
' weak-referencable (%s); use regular Call() instead'
|
||||
' to avoid this warning.',
|
||||
args[0],
|
||||
stack_info=True,
|
||||
)
|
||||
type(self)._did_invalid_call_warning = True
|
||||
self._call = args[0]
|
||||
self._args = args[1:]
|
||||
self._call = call
|
||||
self._args = args
|
||||
self._keywds = keywds
|
||||
|
||||
def __call__(self, *args_extra: Any) -> Any:
|
||||
return self._call(*self._args + args_extra, **self._keywds)
|
||||
def __call__(self, *args_extra: Any, **keywds_extra: Any) -> Any:
|
||||
# Fast path: no extra args or kwargs.
|
||||
if not args_extra and not keywds_extra:
|
||||
return self._call(*self._args, **self._keywds)
|
||||
|
||||
# Slightly slower path: handle extra args.
|
||||
if not keywds_extra:
|
||||
# Only extra positional args; skip dict merge.
|
||||
return self._call(*(self._args + args_extra), **self._keywds)
|
||||
|
||||
# Handle kw overrides (call-time kwargs overriding stored).
|
||||
merged = {**self._keywds, **keywds_extra}
|
||||
return self._call(*(self._args + args_extra), **merged)
|
||||
|
||||
@override
|
||||
def __str__(self) -> str:
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
'<ba.WeakCall object; _call='
|
||||
+ str(self._call)
|
||||
+ ' _args='
|
||||
+ str(self._args)
|
||||
+ ' _keywds='
|
||||
+ str(self._keywds)
|
||||
+ '>'
|
||||
f'<babase.WeakCall object; _call={self._call!r}'
|
||||
f' _args={self._args!r} _keywds={self._keywds!r}>'
|
||||
)
|
||||
|
||||
|
||||
class _Call:
|
||||
"""Wraps a callable and arguments into a single callable object.
|
||||
class CallPartial:
|
||||
"""Wraps a callable and args into a single callable object.
|
||||
|
||||
The callable is strong-referenced so it won't die until this
|
||||
object does.
|
||||
|
||||
Note that a bound method (ex: ``myobj.dosomething``) contains a reference
|
||||
to ``self`` (``myobj`` in that case), so you will be keeping that object
|
||||
alive too. Use babase.WeakCall if you want to pass a method to a callback
|
||||
without keeping its object alive.
|
||||
Note that a bound method (ex: ``myobj.dosomething``) contains a
|
||||
reference to ``self`` (``myobj`` in that case), so you will be
|
||||
keeping that object alive too. Use babase.WeakCall if you want
|
||||
to pass a method to a callback without keeping its object alive.
|
||||
|
||||
Example: Wrap a method call with 1 positional and 1 keyword arg::
|
||||
|
||||
|
|
@ -200,51 +226,218 @@ class _Call:
|
|||
# Optimize performance a bit; we shouldn't need to be super dynamic.
|
||||
__slots__ = ['_call', '_args', '_keywds']
|
||||
|
||||
def __init__(self, *args: Any, **keywds: Any):
|
||||
self._call = args[0]
|
||||
self._args = args[1:]
|
||||
def __init__(self, call: Any, /, *args: Any, **keywds: Any):
|
||||
# Note: keeping _call, _args, _keywds private in this case
|
||||
# since we sub functools.partial for ourself in
|
||||
# type-checking so they will be unrecognized anyway. Use
|
||||
# non-partial versions if you want to access those.
|
||||
self._call = call
|
||||
self._args = args
|
||||
self._keywds = keywds
|
||||
|
||||
def __call__(self, *args_extra: Any) -> Any:
|
||||
return self._call(*self._args + args_extra, **self._keywds)
|
||||
def __call__(self, *args_extra: Any, **keywds_extra: Any) -> Any:
|
||||
# Fast path: no extra args or kwargs.
|
||||
if not args_extra and not keywds_extra:
|
||||
return self._call(*self._args, **self._keywds)
|
||||
|
||||
# Slightly slower path: handle extra args.
|
||||
if not keywds_extra:
|
||||
# Only extra positional args; skip dict merge.
|
||||
return self._call(*(self._args + args_extra), **self._keywds)
|
||||
|
||||
# Handle kw overrides (call-time kwargs overriding stored).
|
||||
merged = {**self._keywds, **keywds_extra}
|
||||
return self._call(*(self._args + args_extra), **merged)
|
||||
|
||||
@override
|
||||
def __str__(self) -> str:
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
'<ba.Call object; _call='
|
||||
+ str(self._call)
|
||||
+ ' _args='
|
||||
+ str(self._args)
|
||||
+ ' _keywds='
|
||||
+ str(self._keywds)
|
||||
+ '>'
|
||||
f'<babase.Call object; _call={self.call!r}'
|
||||
f' _args={self.args!r} _keywds={self.keywds!r}>'
|
||||
)
|
||||
|
||||
class WeakCall:
|
||||
"""Currently alias of :meth:`WeakCallPartial`."""
|
||||
|
||||
# Optimize performance a bit; we shouldn't need to be super dynamic.
|
||||
__slots__ = ['_call', '_args', '_keywds']
|
||||
|
||||
_did_invalid_call_warning = False
|
||||
|
||||
def __init__(self, call: Any, /, *args: Any, **keywds: Any) -> None:
|
||||
warnings.warn(
|
||||
'WeakCall should be replaced with either WeakCallPartial'
|
||||
' (if passing extra args at call time) or WeakCallStrict'
|
||||
' (it not). Once API 9 support ends, WeakCall can again be'
|
||||
' used, but it will behave like WeakCallStrict instead of'
|
||||
' WeakCallPartial.',
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
# Note: keeping _call, _args, _keywds private in this case
|
||||
# since we sub functools.partial for ourself in
|
||||
# type-checking so they will be unrecognized anyway. Use
|
||||
# non-partial versions if you want to access those.
|
||||
if hasattr(call, '__func__'):
|
||||
self._call = WeakMethod(call)
|
||||
else:
|
||||
app = _babase.app
|
||||
if not self._did_invalid_call_warning:
|
||||
logging.warning(
|
||||
'Warning: callable passed to WeakCall() is not'
|
||||
' weak-referencable (%r); use regular Call() instead'
|
||||
' to avoid this warning.',
|
||||
args[0],
|
||||
stack_info=True,
|
||||
)
|
||||
type(self)._did_invalid_call_warning = True
|
||||
self._call = call
|
||||
self._args = args
|
||||
self._keywds = keywds
|
||||
|
||||
def __call__(self, *args_extra: Any, **keywds_extra: Any) -> Any:
|
||||
# Fast path: no extra args or kwargs.
|
||||
if not args_extra and not keywds_extra:
|
||||
return self._call(*self._args, **self._keywds)
|
||||
|
||||
# Slightly slower path: handle extra args.
|
||||
if not keywds_extra:
|
||||
# Only extra positional args; skip dict merge.
|
||||
return self._call(*(self._args + args_extra), **self._keywds)
|
||||
|
||||
# Handle kw overrides (call-time kwargs overriding stored).
|
||||
merged = {**self._keywds, **keywds_extra}
|
||||
return self._call(*(self._args + args_extra), **merged)
|
||||
|
||||
@override
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f'<babase.WeakCall object; _call={self._call!r}'
|
||||
f' _args={self._args!r} _keywds={self._keywds!r}>'
|
||||
)
|
||||
|
||||
class Call:
|
||||
"""Currently alias of :meth:`CallPartial`."""
|
||||
|
||||
# Optimize performance a bit; we shouldn't need to be super dynamic.
|
||||
__slots__ = ['_call', '_args', '_keywds']
|
||||
|
||||
def __init__(self, call: Any, /, *args: Any, **keywds: Any):
|
||||
warnings.warn(
|
||||
'Call should be replaced with either CallPartial'
|
||||
' (if passing extra args at call time) or CallStrict'
|
||||
' (it not). Once API 9 support ends, Call can again be'
|
||||
' used, but it will behave like CallStrict instead'
|
||||
' of CallPartial.',
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
# Note: keeping _call, _args, _keywds private in this case
|
||||
# since we sub functools.partial for ourself in
|
||||
# type-checking so they will be unrecognized anyway. Use
|
||||
# non-partial versions if you want to access those.
|
||||
self._call = call
|
||||
self._args = args
|
||||
self._keywds = keywds
|
||||
|
||||
def __call__(self, *args_extra: Any, **keywds_extra: Any) -> Any:
|
||||
# Fast path: no extra args or kwargs.
|
||||
if not args_extra and not keywds_extra:
|
||||
return self._call(*self._args, **self._keywds)
|
||||
|
||||
# Slightly slower path: handle extra args.
|
||||
if not keywds_extra:
|
||||
# Only extra positional args; skip dict merge.
|
||||
return self._call(*(self._args + args_extra), **self._keywds)
|
||||
|
||||
# Handle kw overrides (call-time kwargs overriding stored).
|
||||
merged = {**self._keywds, **keywds_extra}
|
||||
return self._call(*(self._args + args_extra), **merged)
|
||||
|
||||
@override
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f'<babase.Call object; _call={self.call!r}'
|
||||
f' _args={self.args!r} _keywds={self.keywds!r}>'
|
||||
)
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# For type-checking, point at functools.partial which gives us full
|
||||
# type checking on both positional and keyword arguments (as of mypy
|
||||
# 1.11).
|
||||
# pylint: enable=all
|
||||
|
||||
# FIXME: Actually, currently (as of Dec 2024) mypy doesn't fully
|
||||
# type check partial. The partial() call itself is checked, but the
|
||||
# resulting callable seems to be essentially untyped. We should
|
||||
# probably revise this stuff so that Call and WeakCall are for 100%
|
||||
# complete calls so we can fully type check them using ParamSpecs or
|
||||
# whatnot. We could then write a weak_partial() call if we actually
|
||||
# need that particular combination of functionality.
|
||||
|
||||
# Note: Something here is wonky with pylint, possibly related to our
|
||||
# custom pylint plugin. Disabling all checks seems to fix it.
|
||||
# pylint: disable=all
|
||||
class CallStrict[**P, T]:
|
||||
"""Like :meth:`CallPartial()` but disallows extra args at call time.
|
||||
|
||||
WeakCall = functools.partial
|
||||
Call = functools.partial
|
||||
This allows more complete type checking to occur, so this is
|
||||
recommended if you do not need extra args at call time.
|
||||
"""
|
||||
|
||||
__slots__ = ('call', 'args', 'kwargs')
|
||||
|
||||
def __init__(
|
||||
self, call: Callable[P, T], /, *args: P.args, **kwargs: P.kwargs
|
||||
) -> None:
|
||||
# Note: we allow access to these here since we don't use any
|
||||
# tricks like pointing at functools.partial for type checking or
|
||||
# whatnot that would break this.
|
||||
self.call = call
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
def __call__(self) -> T:
|
||||
return self.call(*self.args, **self.kwargs)
|
||||
|
||||
@override
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f'<babase.Call object; call={self.call!r},'
|
||||
f' args={self.args!r}, kwargs={self.kwargs!r}>'
|
||||
)
|
||||
|
||||
|
||||
class WeakCallStrict[**P, T]:
|
||||
"""Like :meth:`WeakCallPartial()` but disallows extra args at call time.
|
||||
|
||||
This allows more complete type checking to occur, so this is
|
||||
recommended if you do not need extra args at call time.
|
||||
"""
|
||||
|
||||
__slots__ = ('call', 'args', 'kwargs')
|
||||
|
||||
_did_invalid_call_warning = False
|
||||
|
||||
def __init__(
|
||||
self, call: Callable[P, T], /, *args: P.args, **kwargs: P.kwargs
|
||||
) -> None:
|
||||
# Note: we allow access to these here since we don't use any
|
||||
# tricks like pointing at functools.partial for type checking or
|
||||
# whatnot that would break this.
|
||||
if hasattr(call, '__func__'):
|
||||
self.call: Any = WeakMethod(call) # type: ignore
|
||||
else:
|
||||
WeakCall = _WeakCall
|
||||
WeakCall.__name__ = 'WeakCall'
|
||||
Call = _Call
|
||||
Call.__name__ = 'Call'
|
||||
app = _babase.app
|
||||
if not self._did_invalid_call_warning:
|
||||
logging.warning(
|
||||
'Warning: callable passed to WeakCallStrict() is not'
|
||||
' weak-referencable (%r); use regular CallStrict() instead'
|
||||
' to avoid this warning.',
|
||||
args[0],
|
||||
stack_info=True,
|
||||
)
|
||||
type(self)._did_invalid_call_warning = True
|
||||
self.call = call
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
def __call__(self) -> T:
|
||||
return self.call(*self.args, **self.kwargs) # type: ignore
|
||||
|
||||
@override
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f'<babase.WeakCall object; call={self.call!r},'
|
||||
f' args={self.args!r}, kwargs={self.kwargs!r}>'
|
||||
)
|
||||
|
||||
|
||||
class WeakMethod:
|
||||
|
|
@ -255,22 +448,22 @@ class WeakMethod:
|
|||
"""
|
||||
|
||||
# Optimize performance a bit; we shouldn't need to be super dynamic.
|
||||
__slots__ = ['_func', '_obj']
|
||||
__slots__ = ['func', 'obj']
|
||||
|
||||
def __init__(self, call: types.MethodType):
|
||||
assert isinstance(call, types.MethodType)
|
||||
self._func = call.__func__
|
||||
self._obj = weakref.ref(call.__self__)
|
||||
self.func = call.__func__
|
||||
self.obj = weakref.ref(call.__self__)
|
||||
|
||||
def __call__(self, *args: Any, **keywds: Any) -> Any:
|
||||
obj = self._obj()
|
||||
obj: Any = self.obj()
|
||||
if obj is None:
|
||||
return None
|
||||
return self._func(*((obj,) + args), **keywds)
|
||||
return self.func(*((obj,) + args), **keywds)
|
||||
|
||||
@override
|
||||
def __str__(self) -> str:
|
||||
return '<ba.WeakMethod object; call=' + str(self._func) + '>'
|
||||
def __repr__(self) -> str:
|
||||
return f'<babase.WeakMethod object; func={self.func!r}>'
|
||||
|
||||
|
||||
def verify_object_death(obj: object) -> None:
|
||||
|
|
@ -292,7 +485,7 @@ def verify_object_death(obj: object) -> None:
|
|||
# Make this timer in an empty context; don't want it dying with the
|
||||
# scene/etc.
|
||||
with _babase.ContextRef.empty():
|
||||
_babase.apptimer(delay, Call(_verify_object_death, ref))
|
||||
_babase.apptimer(delay, CallStrict(_verify_object_death, ref))
|
||||
|
||||
|
||||
def _verify_object_death(wref: weakref.ref) -> None:
|
||||
|
|
|
|||
3
dist/ba_data/python/babase/_hooks.py
vendored
3
dist/ba_data/python/babase/_hooks.py
vendored
|
|
@ -9,6 +9,7 @@ until it broke at runtime. By instead defining such snippets here and then
|
|||
capturing references to them all at launch it is possible to allow linting
|
||||
and type-checking magic to happen and most issues will be caught immediately.
|
||||
"""
|
||||
|
||||
# (most of these are self-explanatory)
|
||||
# pylint: disable=missing-function-docstring
|
||||
from __future__ import annotations
|
||||
|
|
@ -42,7 +43,7 @@ def get_v2_account_id() -> str | None:
|
|||
if account is not None:
|
||||
accountid = account.accountid
|
||||
# (Avoids mypy complaints when plus is not present)
|
||||
assert isinstance(accountid, (str, type(None)))
|
||||
assert isinstance(accountid, str | None)
|
||||
return accountid
|
||||
return None
|
||||
except Exception:
|
||||
|
|
|
|||
20
dist/ba_data/python/babase/_language.py
vendored
20
dist/ba_data/python/babase/_language.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Language related functionality."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
|
@ -561,7 +562,7 @@ class Lstr:
|
|||
You should avoid doing this as much as possible and instead pass
|
||||
and store ``Lstr`` values.
|
||||
"""
|
||||
return _babase.evaluate_lstr(self._get_json())
|
||||
return _babase.evaluate_lstr(self.as_json())
|
||||
|
||||
def is_flat_value(self) -> bool:
|
||||
"""Return whether this instance represents a 'flat' value.
|
||||
|
|
@ -573,22 +574,13 @@ class Lstr:
|
|||
"""
|
||||
return bool('v' in self.args and not self.args.get('s', []))
|
||||
|
||||
def _get_json(self) -> str:
|
||||
try:
|
||||
def as_json(self) -> str:
|
||||
"""Return the json dict representation of the Lstr."""
|
||||
return json.dumps(self.args, separators=(',', ':'))
|
||||
except Exception:
|
||||
from babase import _error
|
||||
|
||||
applog.exception('_get_json failed for %s.', self.args)
|
||||
return 'JSON_ERR'
|
||||
|
||||
@override
|
||||
def __str__(self) -> str:
|
||||
return f'<ba.Lstr: {self._get_json()}>'
|
||||
|
||||
@override
|
||||
def __repr__(self) -> str:
|
||||
return f'<ba.Lstr: {self._get_json()}>'
|
||||
return f'<babase.Lstr: {self.as_json()}>'
|
||||
|
||||
@staticmethod
|
||||
def from_json(json_string: str) -> babase.Lstr:
|
||||
|
|
@ -616,7 +608,7 @@ def _add_to_attr_dict(dst: AttrDict, src: dict) -> None:
|
|||
)
|
||||
_add_to_attr_dict(dst_dict, value)
|
||||
else:
|
||||
if not isinstance(value, (float, int, bool, str, str, type(None))):
|
||||
if not isinstance(value, float | int | bool | str | None):
|
||||
raise TypeError(
|
||||
"invalid value type for res '"
|
||||
+ key
|
||||
|
|
|
|||
2
dist/ba_data/python/babase/_locale.py
vendored
2
dist/ba_data/python/babase/_locale.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Locale related functionality."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, override, assert_never
|
||||
|
|
@ -144,6 +145,7 @@ class LocaleSubsystem(AppSubsystem):
|
|||
or rlocale is cls.TAMIL
|
||||
or rlocale is cls.THAI
|
||||
or rlocale is cls.VIETNAMESE
|
||||
or rlocale is cls.JAPANESE
|
||||
):
|
||||
# Return True only if we can display full unicode.
|
||||
return _babase.supports_unicode_display()
|
||||
|
|
|
|||
8
dist/ba_data/python/babase/_mgen/enums.py
vendored
8
dist/ba_data/python/babase/_mgen/enums.py
vendored
|
|
@ -85,7 +85,9 @@ class Permission(Enum):
|
|||
|
||||
|
||||
class SpecialChar(Enum):
|
||||
"""Special characters the game can print."""
|
||||
"""Special characters the engine can diplay. Note that this currently
|
||||
needs to be manually kept in sync with bacommon.text.SpecialChar.
|
||||
"""
|
||||
|
||||
DOWN_ARROW = 0
|
||||
UP_ARROW = 1
|
||||
|
|
@ -185,3 +187,7 @@ class SpecialChar(Enum):
|
|||
MIKIROG = 95
|
||||
V2_LOGO = 96
|
||||
CLOSE = 97
|
||||
SANTA_HAT = 98
|
||||
POTATO = 99
|
||||
PALM_TREE = 100
|
||||
BOXING_GLOVE = 101
|
||||
|
|
|
|||
1
dist/ba_data/python/babase/_net.py
vendored
1
dist/ba_data/python/babase/_net.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Networking related functionality."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import socket
|
||||
|
|
|
|||
1
dist/ba_data/python/babase/_ui.py
vendored
1
dist/ba_data/python/babase/_ui.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""UI related bits of babase."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, override
|
||||
|
|
|
|||
1
dist/ba_data/python/babase/modutils.py
vendored
1
dist/ba_data/python/babase/modutils.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to modding."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
|
|
|||
12
dist/ba_data/python/baclassic/_achievement.py
vendored
12
dist/ba_data/python/baclassic/_achievement.py
vendored
|
|
@ -1,12 +1,13 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Various functionality related to achievements."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from bacommon.bs import ClassicChestAppearance
|
||||
from bacommon.classic import ClassicChestAppearance
|
||||
from baclassic._chest import (
|
||||
CHEST_APPEARANCE_DISPLAY_INFOS,
|
||||
CHEST_APPEARANCE_DISPLAY_INFO_DEFAULT,
|
||||
|
|
@ -727,7 +728,10 @@ class Achievement:
|
|||
)
|
||||
|
||||
def get_award_chest_type(self) -> ClassicChestAppearance:
|
||||
"""Return the type of chest given for this achievement."""
|
||||
"""Return the type of chest given for this achievement.
|
||||
|
||||
:meta private:
|
||||
"""
|
||||
|
||||
# For now just map our old ticket values to chest types.
|
||||
# Can add distinct values if need be later.
|
||||
|
|
@ -1520,5 +1524,7 @@ class Achievement:
|
|||
for actor in objs:
|
||||
bascenev1.timer(
|
||||
out_time + 1.000,
|
||||
babase.WeakCall(actor.handlemessage, bascenev1.DieMessage()),
|
||||
babase.WeakCallStrict(
|
||||
actor.handlemessage, bascenev1.DieMessage()
|
||||
),
|
||||
)
|
||||
|
|
|
|||
2
dist/ba_data/python/baclassic/_analytics.py
vendored
2
dist/ba_data/python/baclassic/_analytics.py
vendored
|
|
@ -1,6 +1,6 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to analytics."""
|
||||
"""Functionality related to classic analytics."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
|
|
|||
190
dist/ba_data/python/baclassic/_appmode.py
vendored
190
dist/ba_data/python/baclassic/_appmode.py
vendored
|
|
@ -1,5 +1,6 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
# pylint: disable=too-many-lines
|
||||
"""Contains ClassicAppMode."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
@ -11,11 +12,11 @@ from functools import partial
|
|||
from typing import TYPE_CHECKING, override
|
||||
|
||||
from efro.error import CommunicationError
|
||||
import bacommon.bs
|
||||
import bacommon.clienteffect as clfx
|
||||
import bacommon.classic
|
||||
from babase import AppMode
|
||||
import bauiv1 as bui
|
||||
from bauiv1lib.connectivity import wait_for_connectivity
|
||||
from bauiv1lib.account.signin import show_sign_in_prompt
|
||||
|
||||
import _baclassic
|
||||
|
||||
|
|
@ -233,19 +234,19 @@ class ClassicAppMode(AppMode):
|
|||
|
||||
if item_id.startswith('tokens'):
|
||||
if item_id == 'tokens1':
|
||||
tokens = bacommon.bs.TOKENS1_COUNT
|
||||
tokens = bacommon.classic.TOKENS1_COUNT
|
||||
tokens_str = str(tokens)
|
||||
anim_time = 2.0
|
||||
elif item_id == 'tokens2':
|
||||
tokens = bacommon.bs.TOKENS2_COUNT
|
||||
tokens = bacommon.classic.TOKENS2_COUNT
|
||||
tokens_str = str(tokens)
|
||||
anim_time = 2.5
|
||||
elif item_id == 'tokens3':
|
||||
tokens = bacommon.bs.TOKENS3_COUNT
|
||||
tokens = bacommon.classic.TOKENS3_COUNT
|
||||
tokens_str = str(tokens)
|
||||
anim_time = 3.0
|
||||
elif item_id == 'tokens4':
|
||||
tokens = bacommon.bs.TOKENS4_COUNT
|
||||
tokens = bacommon.classic.TOKENS4_COUNT
|
||||
tokens_str = str(tokens)
|
||||
anim_time = 3.5
|
||||
else:
|
||||
|
|
@ -257,21 +258,19 @@ class ClassicAppMode(AppMode):
|
|||
)
|
||||
|
||||
assert bui.app.classic is not None
|
||||
effects: list[bacommon.bs.ClientEffect] = [
|
||||
bacommon.bs.ClientEffectTokensAnimation(
|
||||
effects: list[clfx.Effect] = [
|
||||
clfx.TokensAnimation(
|
||||
duration=anim_time,
|
||||
startvalue=self._last_tokens_value,
|
||||
endvalue=self._last_tokens_value + tokens,
|
||||
),
|
||||
bacommon.bs.ClientEffectDelay(anim_time),
|
||||
bacommon.bs.ClientEffectScreenMessage(
|
||||
clfx.Delay(anim_time),
|
||||
clfx.LegacyScreenMessage(
|
||||
message='You got ${COUNT} tokens!',
|
||||
subs=['${COUNT}', tokens_str],
|
||||
color=(0, 1, 0),
|
||||
),
|
||||
bacommon.bs.ClientEffectSound(
|
||||
sound=bacommon.bs.ClientEffectSound.Sound.CASH_REGISTER
|
||||
),
|
||||
clfx.PlaySound(clfx.Sound.CASH_REGISTER),
|
||||
]
|
||||
bui.app.classic.run_bs_client_effects(effects)
|
||||
|
||||
|
|
@ -345,14 +344,14 @@ class ClassicAppMode(AppMode):
|
|||
|
||||
with plus.accounts.primary:
|
||||
plus.cloud.send_message_cb(
|
||||
bacommon.bs.GetClassicPurchasesMessage(),
|
||||
on_response=bui.WeakCall(
|
||||
bacommon.classic.GetClassicPurchasesMessage(),
|
||||
on_response=bui.WeakCallPartial(
|
||||
self._on_get_classic_purchases_response
|
||||
),
|
||||
)
|
||||
|
||||
def _on_get_classic_purchases_response(
|
||||
self, response: bacommon.bs.GetClassicPurchasesResponse | Exception
|
||||
self, response: bacommon.classic.GetClassicPurchasesResponse | Exception
|
||||
) -> None:
|
||||
assert self._purchase_request_in_flight
|
||||
self._purchase_request_in_flight = False
|
||||
|
|
@ -471,6 +470,7 @@ class ClassicAppMode(AppMode):
|
|||
chest_1_ad_allow_time=-1.0,
|
||||
chest_2_ad_allow_time=-1.0,
|
||||
chest_3_ad_allow_time=-1.0,
|
||||
store_style='',
|
||||
)
|
||||
self._have_account_values = False
|
||||
self._update_ui_live_state()
|
||||
|
|
@ -505,7 +505,7 @@ class ClassicAppMode(AppMode):
|
|||
print(f'GOT SUB TEST UPDATE: {val}')
|
||||
|
||||
def _on_classic_account_data_change(
|
||||
self, val: bacommon.bs.ClassicAccountLiveData
|
||||
self, val: bacommon.classic.ClassicLiveAccountClientData
|
||||
) -> None:
|
||||
achp = round(val.achievements / max(val.achievements_total, 1) * 100.0)
|
||||
|
||||
|
|
@ -666,6 +666,7 @@ class ClassicAppMode(AppMode):
|
|||
if chest3 is None or chest3.ad_allow_time is None
|
||||
else chest3.ad_allow_time.timestamp()
|
||||
),
|
||||
store_style=val.store_style.value,
|
||||
)
|
||||
|
||||
# Note that we have values and updated faded state accordingly.
|
||||
|
|
@ -723,44 +724,56 @@ class ClassicAppMode(AppMode):
|
|||
def _root_ui_achievements_press(self) -> None:
|
||||
from bauiv1lib.achievements import AchievementsWindow
|
||||
|
||||
if not self._ensure_signed_in_v1():
|
||||
btn = bui.get_special_widget('achievements_button')
|
||||
|
||||
if not self._ensure_signed_in(origin_widget=btn):
|
||||
return
|
||||
|
||||
wait_for_connectivity(
|
||||
on_connected=lambda: bui.app.ui_v1.auxiliary_window_activate(
|
||||
win_type=AchievementsWindow,
|
||||
win_create_call=lambda: AchievementsWindow(
|
||||
origin_widget=bui.get_special_widget('achievements_button')
|
||||
),
|
||||
win_create_call=lambda: AchievementsWindow(origin_widget=btn),
|
||||
)
|
||||
)
|
||||
|
||||
def _root_ui_inbox_press(self) -> None:
|
||||
from bauiv1lib.inbox import InboxWindow
|
||||
|
||||
if not self._ensure_signed_in():
|
||||
btn = bui.get_special_widget('inbox_button')
|
||||
|
||||
if not self._ensure_signed_in(origin_widget=btn):
|
||||
return
|
||||
|
||||
wait_for_connectivity(
|
||||
on_connected=lambda: bui.app.ui_v1.auxiliary_window_activate(
|
||||
win_type=InboxWindow,
|
||||
win_create_call=lambda: InboxWindow(
|
||||
origin_widget=bui.get_special_widget('inbox_button')
|
||||
),
|
||||
win_create_call=lambda: InboxWindow(origin_widget=btn),
|
||||
)
|
||||
)
|
||||
|
||||
def _root_ui_store_press(self) -> None:
|
||||
from bauiv1lib.store.browser import StoreBrowserWindow
|
||||
import bacommon.docui.v1 as dui1
|
||||
|
||||
if not self._ensure_signed_in_v1():
|
||||
from bauiv1lib.docui import DocUIWindow
|
||||
from bauiv1lib.store import StoreUIController
|
||||
|
||||
btn = bui.get_special_widget('store_button')
|
||||
|
||||
if not self._ensure_signed_in(origin_widget=btn):
|
||||
return
|
||||
|
||||
# Pop up an auxiliary window wherever we are in the nav stack.
|
||||
wait_for_connectivity(
|
||||
on_connected=lambda: bui.app.ui_v1.auxiliary_window_activate(
|
||||
win_type=StoreBrowserWindow,
|
||||
win_create_call=lambda: StoreBrowserWindow(
|
||||
origin_widget=bui.get_special_widget('store_button')
|
||||
win_type=DocUIWindow,
|
||||
win_create_call=bui.CallStrict(
|
||||
StoreUIController().create_window,
|
||||
dui1.Request('/'),
|
||||
origin_widget=btn,
|
||||
uiopenstateid='classicstore',
|
||||
),
|
||||
win_extra_type_id=(
|
||||
StoreUIController.get_window_extra_type_id()
|
||||
),
|
||||
)
|
||||
)
|
||||
|
|
@ -782,80 +795,76 @@ class ClassicAppMode(AppMode):
|
|||
def _root_ui_trophy_meter_press(self) -> None:
|
||||
from bauiv1lib.league.rankwindow import LeagueRankWindow
|
||||
|
||||
if not self._ensure_signed_in_v1():
|
||||
btn = bui.get_special_widget('trophy_meter')
|
||||
|
||||
if not self._ensure_signed_in(origin_widget=btn):
|
||||
return
|
||||
|
||||
bui.app.ui_v1.auxiliary_window_activate(
|
||||
win_type=LeagueRankWindow,
|
||||
win_create_call=lambda: LeagueRankWindow(
|
||||
origin_widget=bui.get_special_widget('trophy_meter')
|
||||
),
|
||||
win_create_call=lambda: LeagueRankWindow(origin_widget=btn),
|
||||
)
|
||||
|
||||
def _root_ui_level_meter_press(self) -> None:
|
||||
from bauiv1lib.resourcetypeinfo import ResourceTypeInfoWindow
|
||||
|
||||
ResourceTypeInfoWindow(
|
||||
'xp', origin_widget=bui.get_special_widget('level_meter')
|
||||
)
|
||||
btn = bui.get_special_widget('level_meter')
|
||||
|
||||
def _root_ui_inventory_press(self) -> None:
|
||||
from bauiv1lib.inventory import InventoryWindow
|
||||
|
||||
if not self._ensure_signed_in_v1():
|
||||
if not self._ensure_signed_in(origin_widget=btn):
|
||||
return
|
||||
|
||||
ResourceTypeInfoWindow('xp', origin_widget=btn)
|
||||
|
||||
def _root_ui_inventory_press(self) -> None:
|
||||
import bacommon.docui.v1 as dui1
|
||||
|
||||
from bauiv1lib.docui import DocUIWindow
|
||||
from bauiv1lib.inventory import InventoryUIController
|
||||
|
||||
# Pop up an auxiliary window wherever we are in the nav stack.
|
||||
bui.app.ui_v1.auxiliary_window_activate(
|
||||
win_type=InventoryWindow,
|
||||
win_create_call=lambda: InventoryWindow(
|
||||
origin_widget=bui.get_special_widget('inventory_button')
|
||||
win_type=DocUIWindow,
|
||||
win_create_call=bui.CallStrict(
|
||||
InventoryUIController().create_window,
|
||||
dui1.Request('/'),
|
||||
origin_widget=bui.get_special_widget('inventory_button'),
|
||||
uiopenstateid='classicinventory',
|
||||
),
|
||||
win_extra_type_id=InventoryUIController.get_window_extra_type_id(),
|
||||
)
|
||||
|
||||
def _ensure_signed_in(self) -> bool:
|
||||
def _ensure_signed_in(self, *, origin_widget: bui.Widget | None) -> bool:
|
||||
"""Make sure we're signed in (requiring modern v2 accounts)."""
|
||||
from bauiv1lib.account.signin import show_sign_in_prompt
|
||||
|
||||
plus = bui.app.plus
|
||||
if plus is None:
|
||||
bui.screenmessage('This requires plus.', color=(1, 0, 0))
|
||||
bui.getsound('error').play()
|
||||
return False
|
||||
if plus.accounts.primary is None:
|
||||
show_sign_in_prompt()
|
||||
return False
|
||||
return True
|
||||
|
||||
def _ensure_signed_in_v1(self) -> bool:
|
||||
"""Make sure we're signed in (allowing legacy v1-only accounts)."""
|
||||
plus = bui.app.plus
|
||||
if plus is None:
|
||||
bui.screenmessage('This requires plus.', color=(1, 0, 0))
|
||||
bui.getsound('error').play()
|
||||
return False
|
||||
if plus.get_v1_account_state() != 'signed_in':
|
||||
show_sign_in_prompt()
|
||||
show_sign_in_prompt(origin_widget=origin_widget)
|
||||
return False
|
||||
return True
|
||||
|
||||
def _root_ui_get_tokens_press(self) -> None:
|
||||
from bauiv1lib.gettokens import GetTokensWindow
|
||||
from bauiv1lib.gettokens import GetTokensWindow, show_get_tokens_window
|
||||
|
||||
if not self._ensure_signed_in():
|
||||
btn = bui.get_special_widget('get_tokens_button')
|
||||
|
||||
if not self._ensure_signed_in(origin_widget=btn):
|
||||
return
|
||||
|
||||
if bool(True):
|
||||
show_get_tokens_window(origin_widget=btn, toggle=True)
|
||||
else:
|
||||
bui.app.ui_v1.auxiliary_window_activate(
|
||||
win_type=GetTokensWindow,
|
||||
win_create_call=lambda: GetTokensWindow(
|
||||
origin_widget=bui.get_special_widget('get_tokens_button')
|
||||
),
|
||||
win_create_call=lambda: GetTokensWindow(origin_widget=btn),
|
||||
)
|
||||
|
||||
def _root_ui_chest_slot_pressed(self, index: int) -> None:
|
||||
from bauiv1lib.chest import (
|
||||
ChestWindow0,
|
||||
ChestWindow1,
|
||||
ChestWindow2,
|
||||
ChestWindow3,
|
||||
)
|
||||
from bauiv1lib.chest import ChestWindow
|
||||
|
||||
widgetid: Literal[
|
||||
'chest_0_button',
|
||||
|
|
@ -866,16 +875,20 @@ class ClassicAppMode(AppMode):
|
|||
winclass: type[ChestWindow]
|
||||
if index == 0:
|
||||
widgetid = 'chest_0_button'
|
||||
winclass = ChestWindow0
|
||||
winclass = ChestWindow
|
||||
extratypeid = '0'
|
||||
elif index == 1:
|
||||
widgetid = 'chest_1_button'
|
||||
winclass = ChestWindow1
|
||||
winclass = ChestWindow
|
||||
extratypeid = '1'
|
||||
elif index == 2:
|
||||
widgetid = 'chest_2_button'
|
||||
winclass = ChestWindow2
|
||||
winclass = ChestWindow
|
||||
extratypeid = '2'
|
||||
elif index == 3:
|
||||
widgetid = 'chest_3_button'
|
||||
winclass = ChestWindow3
|
||||
winclass = ChestWindow
|
||||
extratypeid = '3'
|
||||
else:
|
||||
raise RuntimeError(f'Invalid index {index}')
|
||||
|
||||
|
|
@ -886,6 +899,7 @@ class ClassicAppMode(AppMode):
|
|||
index=index,
|
||||
origin_widget=bui.get_special_widget(widgetid),
|
||||
),
|
||||
win_extra_type_id=extratypeid,
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -953,16 +967,26 @@ class ClassicAppMode(AppMode):
|
|||
return [
|
||||
bui.DevConsoleButtonDef(
|
||||
'MainWindow Template',
|
||||
bui.WeakCall(self._main_win_template_press),
|
||||
bui.WeakCallStrict(self._main_win_template_press),
|
||||
),
|
||||
bui.DevConsoleButtonDef(
|
||||
'CloudUI Test', bui.WeakCall(self._cloud_ui_test_press)
|
||||
'DocUI Test', bui.WeakCallStrict(self._doc_ui_test_press)
|
||||
),
|
||||
]
|
||||
|
||||
def _main_win_template_press(self) -> None:
|
||||
from bauiv1lib.template import show_template_main_window
|
||||
|
||||
# This only works if a main ui is up.
|
||||
if bui.app.ui_v1.get_main_window() is None:
|
||||
bui.screenmessage(
|
||||
'This requires a main-window to be present.'
|
||||
' Open a menu or whatnot first.',
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
bui.getsound('error').play()
|
||||
return
|
||||
|
||||
# Unintuitively, swish sounds come from buttons, not windows.
|
||||
# And dev-console buttons don't make sounds. So we need to
|
||||
# explicitly do so here.
|
||||
|
|
@ -970,12 +994,22 @@ class ClassicAppMode(AppMode):
|
|||
|
||||
show_template_main_window()
|
||||
|
||||
def _cloud_ui_test_press(self) -> None:
|
||||
from bauiv1 import show_cloud_ui_window
|
||||
def _doc_ui_test_press(self) -> None:
|
||||
from bauiv1lib.docuitest import show_test_doc_ui_window
|
||||
|
||||
# This only works if a main ui is up.
|
||||
if bui.app.ui_v1.get_main_window() is None:
|
||||
bui.screenmessage(
|
||||
'This requires a main-window to be present.'
|
||||
' Open a menu or whatnot first.',
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
bui.getsound('error').play()
|
||||
return
|
||||
|
||||
# Unintuitively, swish sounds come from buttons, not windows.
|
||||
# And dev-console buttons don't make sounds. So we need to
|
||||
# explicitly do so here.
|
||||
bui.getsound('swish').play()
|
||||
|
||||
show_cloud_ui_window()
|
||||
show_test_doc_ui_window()
|
||||
|
|
|
|||
49
dist/ba_data/python/baclassic/_appsubsystem.py
vendored
49
dist/ba_data/python/baclassic/_appsubsystem.py
vendored
|
|
@ -3,6 +3,7 @@
|
|||
# pylint: disable=too-many-lines
|
||||
|
||||
"""Provides classic app subsystem."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
|
|
@ -27,7 +28,9 @@ from baclassic import _input
|
|||
if TYPE_CHECKING:
|
||||
from typing import Callable, Any, Sequence
|
||||
|
||||
import bacommon.bs
|
||||
import bacommon.classic
|
||||
import bacommon.clienteffect as clfx
|
||||
import bacommon.clouddialog.basic as bcdlg
|
||||
from bascenev1lib.actor import spazappearance
|
||||
from bauiv1lib.party import PartyWindow
|
||||
|
||||
|
|
@ -408,7 +411,7 @@ class ClassicAppSubsystem(babase.AppSubsystem):
|
|||
# Otherwise just force the issue.
|
||||
else:
|
||||
babase.pushcall(
|
||||
babase.Call(bascenev1.new_host_session, MainMenuSession)
|
||||
babase.CallStrict(bascenev1.new_host_session, MainMenuSession)
|
||||
)
|
||||
|
||||
def getmaps(self, playtype: str) -> list[str]:
|
||||
|
|
@ -702,7 +705,7 @@ class ClassicAppSubsystem(babase.AppSubsystem):
|
|||
if sddata is not None:
|
||||
babase.apptimer(
|
||||
delay,
|
||||
babase.Call(ServerDialogWindow, sddata),
|
||||
babase.CallStrict(ServerDialogWindow, sddata),
|
||||
)
|
||||
|
||||
def show_url_window(self, address: str) -> None:
|
||||
|
|
@ -751,10 +754,13 @@ class ClassicAppSubsystem(babase.AppSubsystem):
|
|||
self,
|
||||
transition: str = 'in_right',
|
||||
origin_widget: bauiv1.Widget | None = None,
|
||||
selected_profile: str | None = None,
|
||||
# selected_profile: str | None = None,
|
||||
) -> None:
|
||||
"""Pop up a browser window from within a game."""
|
||||
from bauiv1lib.profile.browser import ProfileBrowserWindow
|
||||
import bacommon.docui.v1 as dui1
|
||||
|
||||
# from bauiv1lib.profile.browser import ProfileBrowserWindow
|
||||
from bauiv1lib.inventory import InventoryUIController
|
||||
|
||||
main_window = babase.app.ui_v1.get_main_window()
|
||||
if main_window is not None:
|
||||
|
|
@ -765,15 +771,16 @@ class ClassicAppSubsystem(babase.AppSubsystem):
|
|||
return
|
||||
|
||||
babase.app.ui_v1.set_main_window(
|
||||
ProfileBrowserWindow(
|
||||
InventoryUIController(player_profiles_only=True).create_window(
|
||||
dui1.Request('/'),
|
||||
uiopenstateid='classicinventory',
|
||||
transition=transition,
|
||||
selected_profile=selected_profile,
|
||||
origin_widget=origin_widget,
|
||||
minimal_toolbar=True,
|
||||
),
|
||||
is_top_level=True,
|
||||
back_state=None,
|
||||
suppress_warning=True,
|
||||
extra_type_id=InventoryUIController.get_window_extra_type_id(),
|
||||
)
|
||||
|
||||
def preload_map_preview_media(self) -> None:
|
||||
|
|
@ -835,6 +842,7 @@ class ClassicAppSubsystem(babase.AppSubsystem):
|
|||
suppress_warning=True,
|
||||
# Reset selections to default for consistency.
|
||||
restore_shared_state=False,
|
||||
extra_type_id='',
|
||||
)
|
||||
|
||||
def save_ui_state(self) -> None:
|
||||
|
|
@ -876,11 +884,17 @@ class ClassicAppSubsystem(babase.AppSubsystem):
|
|||
is_top_level=True,
|
||||
back_state=None,
|
||||
suppress_warning=True,
|
||||
extra_type_id='',
|
||||
)
|
||||
else:
|
||||
# If there's a saved ui state, restore that.
|
||||
if self.saved_ui_state is not None:
|
||||
app.ui_v1.restore_main_window_state(self.saved_ui_state)
|
||||
# Kill the state now that we're back; we'll
|
||||
# generate a new one when we leave. This keeps
|
||||
# UIOpenStates stored in the state doing the
|
||||
# right thing.
|
||||
self.saved_ui_state = None
|
||||
else:
|
||||
# Otherwise start fresh at the main menu.
|
||||
from bauiv1lib.mainmenu import MainMenuWindow
|
||||
|
|
@ -890,25 +904,32 @@ class ClassicAppSubsystem(babase.AppSubsystem):
|
|||
is_top_level=True,
|
||||
back_state=None,
|
||||
suppress_warning=True,
|
||||
extra_type_id='',
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def run_bs_client_effects(
|
||||
effects: list[bacommon.bs.ClientEffect], delay: float = 0.0
|
||||
effects: list[clfx.Effect], delay: float = 0.0
|
||||
) -> None:
|
||||
"""Run client effects sent from the master server."""
|
||||
"""Run client effects sent from the master server.
|
||||
|
||||
:meta private:
|
||||
"""
|
||||
from baclassic._clienteffect import run_bs_client_effects
|
||||
|
||||
run_bs_client_effects(effects, delay=delay)
|
||||
|
||||
@staticmethod
|
||||
def basic_client_ui_button_label_str(
|
||||
label: bacommon.bs.BasicCloudDialog.ButtonLabel,
|
||||
label: bcdlg.ButtonLabel,
|
||||
) -> babase.Lstr:
|
||||
"""Given a client-ui label, return an Lstr."""
|
||||
import bacommon.bs
|
||||
"""Given a client-ui label, return an Lstr.
|
||||
|
||||
cls = bacommon.bs.BasicCloudDialog.ButtonLabel
|
||||
:meta private:
|
||||
"""
|
||||
import bacommon.clouddialog.basic as bcdlg
|
||||
|
||||
cls = bcdlg.ButtonLabel
|
||||
if label is cls.UNKNOWN:
|
||||
# Server should not be sending us unknown stuff; make noise
|
||||
# if they do.
|
||||
|
|
|
|||
23
dist/ba_data/python/baclassic/_benchmark.py
vendored
23
dist/ba_data/python/baclassic/_benchmark.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Benchmark/Stress-Test related functionality."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
|
|
@ -130,9 +131,9 @@ def _start_stress_test(args: _StressTestArgs) -> None:
|
|||
appconfig['Team Tournament Playlist Randomize'] = 1
|
||||
babase.apptimer(
|
||||
1.0,
|
||||
babase.Call(
|
||||
babase.CallStrict(
|
||||
babase.pushcall,
|
||||
babase.Call(bascenev1.new_host_session, DualTeamSession),
|
||||
babase.CallStrict(bascenev1.new_host_session, DualTeamSession),
|
||||
),
|
||||
)
|
||||
else:
|
||||
|
|
@ -140,18 +141,22 @@ def _start_stress_test(args: _StressTestArgs) -> None:
|
|||
appconfig['Free-for-All Playlist Randomize'] = 1
|
||||
babase.apptimer(
|
||||
1.0,
|
||||
babase.Call(
|
||||
babase.CallStrict(
|
||||
babase.pushcall,
|
||||
babase.Call(bascenev1.new_host_session, FreeForAllSession),
|
||||
babase.CallStrict(
|
||||
bascenev1.new_host_session, FreeForAllSession
|
||||
),
|
||||
),
|
||||
)
|
||||
_baclassic.set_stress_testing(True, args.player_count, args.attract_mode)
|
||||
classic.stress_test_update_timer = babase.AppTimer(
|
||||
args.round_duration, babase.Call(_reset_stress_test, args)
|
||||
args.round_duration, babase.CallStrict(_reset_stress_test, args)
|
||||
)
|
||||
if args.attract_mode:
|
||||
classic.stress_test_update_timer_2 = babase.AppTimer(
|
||||
0.48, babase.Call(_update_attract_mode_test, args), repeat=True
|
||||
0.48,
|
||||
babase.CallStrict(_update_attract_mode_test, args),
|
||||
repeat=True,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -176,7 +181,7 @@ def _reset_stress_test(args: _StressTestArgs) -> None:
|
|||
# we just end back at the main menu. If things are idle there then
|
||||
# we'll get sent back to a new stress test.
|
||||
if not args.attract_mode:
|
||||
babase.apptimer(1.0, babase.Call(_start_stress_test, args))
|
||||
babase.apptimer(1.0, babase.CallStrict(_start_stress_test, args))
|
||||
|
||||
|
||||
def run_media_reload_benchmark() -> None:
|
||||
|
|
@ -200,8 +205,8 @@ def run_media_reload_benchmark() -> None:
|
|||
color=(1, 1, 0),
|
||||
)
|
||||
|
||||
babase.add_clean_frame_callback(babase.Call(doit, start_time))
|
||||
babase.add_clean_frame_callback(babase.CallStrict(doit, start_time))
|
||||
|
||||
# The reload starts (should add a completion callback to the reload
|
||||
# func to fix this).
|
||||
babase.apptimer(0.05, babase.Call(delay_add, babase.apptime()))
|
||||
babase.apptimer(0.05, babase.CallStrict(delay_add, babase.apptime()))
|
||||
|
|
|
|||
3
dist/ba_data/python/baclassic/_chest.py
vendored
3
dist/ba_data/python/baclassic/_chest.py
vendored
|
|
@ -1,12 +1,13 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Chest related functionality."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from bacommon.bs import ClassicChestAppearance
|
||||
from bacommon.classic import ClassicChestAppearance
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
|
|
|||
63
dist/ba_data/python/baclassic/_clienteffect.py
vendored
63
dist/ba_data/python/baclassic/_clienteffect.py
vendored
|
|
@ -9,26 +9,25 @@ from typing import TYPE_CHECKING, assert_never
|
|||
|
||||
from efro.util import strict_partial
|
||||
|
||||
import bacommon.bs
|
||||
import bauiv1
|
||||
|
||||
import _baclassic
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
import bacommon.clienteffect as clfx
|
||||
|
||||
|
||||
def run_bs_client_effects(
|
||||
effects: list[bacommon.bs.ClientEffect], delay: float = 0.0
|
||||
effects: list[clfx.Effect], delay: float = 0.0
|
||||
) -> None:
|
||||
"""Run effects."""
|
||||
# pylint: disable=too-many-branches
|
||||
from bacommon.bs import ClientEffectTypeID
|
||||
import bacommon.clienteffect as clfx
|
||||
|
||||
for effect in effects:
|
||||
effecttype = effect.get_type_id()
|
||||
if effecttype is ClientEffectTypeID.SCREEN_MESSAGE:
|
||||
assert isinstance(effect, bacommon.bs.ClientEffectScreenMessage)
|
||||
if effecttype is clfx.EffectTypeID.LEGACY_SCREEN_MESSAGE:
|
||||
assert isinstance(effect, clfx.LegacyScreenMessage)
|
||||
textfin = bauiv1.Lstr(
|
||||
translate=('serverResponses', effect.message)
|
||||
).evaluate()
|
||||
|
|
@ -46,22 +45,33 @@ def run_bs_client_effects(
|
|||
bauiv1.screenmessage, textfin, color=effect.color
|
||||
),
|
||||
)
|
||||
elif effecttype is clfx.EffectTypeID.SCREEN_MESSAGE:
|
||||
assert isinstance(effect, clfx.ScreenMessage)
|
||||
bauiv1.apptimer(
|
||||
delay,
|
||||
strict_partial(
|
||||
bauiv1.screenmessage,
|
||||
effect.message,
|
||||
color=effect.color,
|
||||
literal=not effect.is_lstr,
|
||||
),
|
||||
)
|
||||
|
||||
elif effecttype is ClientEffectTypeID.SOUND:
|
||||
assert isinstance(effect, bacommon.bs.ClientEffectSound)
|
||||
smcls = bacommon.bs.ClientEffectSound.Sound
|
||||
elif effecttype is clfx.EffectTypeID.SOUND:
|
||||
assert isinstance(effect, clfx.PlaySound)
|
||||
scls = clfx.Sound
|
||||
soundfile: str | None = None
|
||||
if effect.sound is smcls.UNKNOWN:
|
||||
if effect.sound is scls.UNKNOWN:
|
||||
# Server should avoid sending us sounds we don't
|
||||
# support. Make some noise if it happens.
|
||||
logging.error('Got unrecognized bacommon.bs.ClientEffectSound.')
|
||||
elif effect.sound is smcls.CASH_REGISTER:
|
||||
logging.error('Got unrecognized bacommon.classic.Sound.')
|
||||
elif effect.sound is scls.CASH_REGISTER:
|
||||
soundfile = 'cashRegister'
|
||||
elif effect.sound is smcls.ERROR:
|
||||
elif effect.sound is scls.ERROR:
|
||||
soundfile = 'error'
|
||||
elif effect.sound is smcls.POWER_DOWN:
|
||||
elif effect.sound is scls.POWER_DOWN:
|
||||
soundfile = 'powerdown01'
|
||||
elif effect.sound is smcls.GUN_COCKING:
|
||||
elif effect.sound is scls.GUN_COCKING:
|
||||
soundfile = 'gunCocking'
|
||||
else:
|
||||
assert_never(effect.sound)
|
||||
|
|
@ -73,14 +83,12 @@ def run_bs_client_effects(
|
|||
),
|
||||
)
|
||||
|
||||
elif effecttype is ClientEffectTypeID.DELAY:
|
||||
assert isinstance(effect, bacommon.bs.ClientEffectDelay)
|
||||
elif effecttype is clfx.EffectTypeID.DELAY:
|
||||
assert isinstance(effect, clfx.Delay)
|
||||
delay += effect.seconds
|
||||
|
||||
elif effecttype is ClientEffectTypeID.CHEST_WAIT_TIME_ANIMATION:
|
||||
assert isinstance(
|
||||
effect, bacommon.bs.ClientEffectChestWaitTimeAnimation
|
||||
)
|
||||
elif effecttype is clfx.EffectTypeID.CHEST_WAIT_TIME_ANIMATION:
|
||||
assert isinstance(effect, clfx.ChestWaitTimeAnimation)
|
||||
bauiv1.apptimer(
|
||||
delay,
|
||||
strict_partial(
|
||||
|
|
@ -92,8 +100,8 @@ def run_bs_client_effects(
|
|||
),
|
||||
)
|
||||
|
||||
elif effecttype is ClientEffectTypeID.TICKETS_ANIMATION:
|
||||
assert isinstance(effect, bacommon.bs.ClientEffectTicketsAnimation)
|
||||
elif effecttype is clfx.EffectTypeID.TICKETS_ANIMATION:
|
||||
assert isinstance(effect, clfx.TicketsAnimation)
|
||||
bauiv1.apptimer(
|
||||
delay,
|
||||
strict_partial(
|
||||
|
|
@ -104,8 +112,8 @@ def run_bs_client_effects(
|
|||
),
|
||||
)
|
||||
|
||||
elif effecttype is ClientEffectTypeID.TOKENS_ANIMATION:
|
||||
assert isinstance(effect, bacommon.bs.ClientEffectTokensAnimation)
|
||||
elif effecttype is clfx.EffectTypeID.TOKENS_ANIMATION:
|
||||
assert isinstance(effect, clfx.TokensAnimation)
|
||||
bauiv1.apptimer(
|
||||
delay,
|
||||
strict_partial(
|
||||
|
|
@ -116,12 +124,11 @@ def run_bs_client_effects(
|
|||
),
|
||||
)
|
||||
|
||||
elif effecttype is ClientEffectTypeID.UNKNOWN:
|
||||
elif effecttype is clfx.EffectTypeID.UNKNOWN:
|
||||
# Server should not send us stuff we can't digest. Make
|
||||
# some noise if it happens.
|
||||
logging.error(
|
||||
'Got unrecognized bacommon.bs.ClientEffect;'
|
||||
' should not happen.'
|
||||
'Got unrecognized bacommon.classic.Effect; should not happen.'
|
||||
)
|
||||
|
||||
else:
|
||||
|
|
|
|||
39
dist/ba_data/python/baclassic/_displayitem.py
vendored
39
dist/ba_data/python/baclassic/_displayitem.py
vendored
|
|
@ -1,28 +1,33 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Display-item related functionality."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, assert_never
|
||||
|
||||
from efro.util import pairs_from_flat
|
||||
import bacommon.bs
|
||||
import bacommon.displayitem as ditm
|
||||
import bacommon.classic
|
||||
import bauiv1
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
|
||||
# FIXME - migrate to use the doc-ui rendering for these instead.
|
||||
def show_display_item(
|
||||
itemwrapper: bacommon.bs.DisplayItemWrapper,
|
||||
itemwrapper: ditm.Wrapper,
|
||||
parent: bauiv1.Widget,
|
||||
pos: tuple[float, float],
|
||||
width: float,
|
||||
debug: bool = False,
|
||||
) -> None:
|
||||
"""Create ui to depict a display-item."""
|
||||
# pylint: disable=too-many-locals
|
||||
|
||||
height = width * 0.666
|
||||
# Let's go with 4:3 aspect ratio.
|
||||
height = width * 0.75
|
||||
|
||||
# Silent no-op if our parent ui is dead.
|
||||
if not parent:
|
||||
|
|
@ -33,15 +38,24 @@ def show_display_item(
|
|||
text_y_offs = 0.0
|
||||
show_text = True
|
||||
|
||||
if isinstance(itemwrapper.item, bacommon.bs.TicketsDisplayItem):
|
||||
itemtype = itemwrapper.item.get_type_id()
|
||||
|
||||
if itemtype is ditm.ItemTypeID.TICKETS:
|
||||
img = 'tickets'
|
||||
img_y_offs = width * 0.11
|
||||
text_y_offs = width * -0.15
|
||||
elif isinstance(itemwrapper.item, bacommon.bs.TokensDisplayItem):
|
||||
elif itemtype is ditm.ItemTypeID.TICKETS_PURPLE:
|
||||
img = 'ticketsPurple'
|
||||
img_y_offs = width * 0.11
|
||||
text_y_offs = width * -0.15
|
||||
elif itemtype is ditm.ItemTypeID.TOKENS:
|
||||
img = 'coin'
|
||||
img_y_offs = width * 0.11
|
||||
text_y_offs = width * -0.15
|
||||
elif isinstance(itemwrapper.item, bacommon.bs.ChestDisplayItem):
|
||||
elif itemtype is ditm.ItemTypeID.CHEST:
|
||||
assert isinstance(
|
||||
itemwrapper.item, bacommon.classic.ClassicChestDisplayItem
|
||||
)
|
||||
from baclassic._chest import (
|
||||
CHEST_APPEARANCE_DISPLAY_INFOS,
|
||||
CHEST_APPEARANCE_DISPLAY_INFO_DEFAULT,
|
||||
|
|
@ -63,9 +77,14 @@ def show_display_item(
|
|||
tint_color=c_info.tint,
|
||||
tint2_color=c_info.tint2,
|
||||
)
|
||||
elif (
|
||||
itemtype is ditm.ItemTypeID.TEST or itemtype is ditm.ItemTypeID.UNKNOWN
|
||||
):
|
||||
pass
|
||||
else:
|
||||
assert_never(itemtype)
|
||||
|
||||
# Enable this for testing spacing.
|
||||
if bool(False):
|
||||
if debug:
|
||||
bauiv1.imagewidget(
|
||||
parent=parent,
|
||||
position=(
|
||||
|
|
|
|||
1
dist/ba_data/python/baclassic/_hooks.py
vendored
1
dist/ba_data/python/baclassic/_hooks.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Hooks for C++ layer to use for ClassicAppMode."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
|
|
|||
1
dist/ba_data/python/baclassic/_input.py
vendored
1
dist/ba_data/python/baclassic/_input.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Input related functionality"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
|
|
|||
1
dist/ba_data/python/baclassic/_music.py
vendored
1
dist/ba_data/python/baclassic/_music.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Music related functionality."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
|
|
|
|||
7
dist/ba_data/python/baclassic/_net.py
vendored
7
dist/ba_data/python/baclassic/_net.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Networking related functionality."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import zlib
|
||||
|
|
@ -14,7 +15,7 @@ from typing import TYPE_CHECKING, override
|
|||
|
||||
from efro.error import CommunicationError
|
||||
from efro.util import strip_exception_tracebacks
|
||||
import bacommon.bs
|
||||
import bacommon.classic
|
||||
import babase
|
||||
import bascenev1
|
||||
|
||||
|
|
@ -113,7 +114,7 @@ class MasterServerV1CallThread(threading.Thread):
|
|||
dataenc = urllib.parse.urlencode(self._data)
|
||||
|
||||
mresponse = plus.cloud.send_message(
|
||||
bacommon.bs.LegacyRequest(
|
||||
bacommon.classic.LegacyRequest(
|
||||
self._request,
|
||||
self._request_type,
|
||||
classic.legacy_user_agent_string,
|
||||
|
|
@ -168,7 +169,7 @@ class MasterServerV1CallThread(threading.Thread):
|
|||
|
||||
if self._callback is not None:
|
||||
babase.pushcall(
|
||||
babase.Call(self._run_callback, response_data),
|
||||
babase.CallStrict(self._run_callback, response_data),
|
||||
from_other_thread=True,
|
||||
)
|
||||
|
||||
|
|
|
|||
1
dist/ba_data/python/baclassic/_servermode.py
vendored
1
dist/ba_data/python/baclassic/_servermode.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to running the game in server-mode."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
|
|
|||
18
dist/ba_data/python/baclassic/_store.py
vendored
18
dist/ba_data/python/baclassic/_store.py
vendored
|
|
@ -292,6 +292,18 @@ class StoreSubsystem:
|
|||
'icons.explodinary': {
|
||||
'icon': babase.charstr(babase.SpecialChar.EXPLODINARY_LOGO)
|
||||
},
|
||||
'icons.santa_hat': {
|
||||
'icon': babase.charstr(babase.SpecialChar.SANTA_HAT)
|
||||
},
|
||||
'icons.potato': {
|
||||
'icon': babase.charstr(babase.SpecialChar.POTATO)
|
||||
},
|
||||
'icons.palm_tree': {
|
||||
'icon': babase.charstr(babase.SpecialChar.PALM_TREE)
|
||||
},
|
||||
'icons.boxing_glove': {
|
||||
'icon': babase.charstr(babase.SpecialChar.BOXING_GLOVE)
|
||||
},
|
||||
}
|
||||
return babase.app.classic.store_items
|
||||
|
||||
|
|
@ -569,7 +581,7 @@ class StoreSubsystem:
|
|||
def get_unowned_maps(self) -> list[str]:
|
||||
"""Return the list of local maps not owned by the current account."""
|
||||
classic = babase.app.classic
|
||||
purchases = classic.purchases if classic is not None else set()
|
||||
purchases = classic.purchases if classic is not None else frozenset()
|
||||
unowned_maps: set[str] = set()
|
||||
if babase.app.env.gui:
|
||||
for map_section in self.get_store_layout()['maps']:
|
||||
|
|
@ -583,7 +595,9 @@ class StoreSubsystem:
|
|||
"""Return present game types not owned by the current account."""
|
||||
try:
|
||||
classic = babase.app.classic
|
||||
purchases = classic.purchases if classic is not None else set()
|
||||
purchases = (
|
||||
classic.purchases if classic is not None else frozenset()
|
||||
)
|
||||
unowned_games: set[type[bascenev1.GameActivity]] = set()
|
||||
if babase.app.env.gui:
|
||||
for section in self.get_store_layout()['minigames']:
|
||||
|
|
|
|||
1
dist/ba_data/python/baclassic/_tips.py
vendored
1
dist/ba_data/python/baclassic/_tips.py
vendored
|
|
@ -3,6 +3,7 @@
|
|||
"""Functionality related to classic game tips.
|
||||
|
||||
These can be shown at opportune times such as between rounds."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
|
|
|||
2
dist/ba_data/python/baclassic/_tournament.py
vendored
2
dist/ba_data/python/baclassic/_tournament.py
vendored
|
|
@ -6,7 +6,7 @@ from __future__ import annotations
|
|||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from bacommon.bs import ClassicChestAppearance
|
||||
from bacommon.classic import ClassicChestAppearance
|
||||
import babase
|
||||
import bauiv1
|
||||
import bascenev1
|
||||
|
|
|
|||
9
dist/ba_data/python/baclassic/macmusicapp.py
vendored
9
dist/ba_data/python/baclassic/macmusicapp.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Music playback functionality using the Mac Music (formerly iTunes) app."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
|
@ -95,7 +96,7 @@ class _MacMusicAppThread(threading.Thread):
|
|||
def do_print() -> None:
|
||||
babase.apptimer(
|
||||
0.5,
|
||||
babase.Call(
|
||||
babase.CallStrict(
|
||||
babase.screenmessage,
|
||||
babase.Lstr(resource='usingItunesText'),
|
||||
(0, 1, 0),
|
||||
|
|
@ -198,7 +199,9 @@ class _MacMusicAppThread(threading.Thread):
|
|||
except Exception as exc:
|
||||
print('Error getting iTunes playlists:', exc)
|
||||
playlists = []
|
||||
babase.pushcall(babase.Call(target, playlists), from_other_thread=True)
|
||||
babase.pushcall(
|
||||
babase.CallStrict(target, playlists), from_other_thread=True
|
||||
)
|
||||
|
||||
def _handle_play_command(self, target: str | None) -> None:
|
||||
if target is None:
|
||||
|
|
@ -246,7 +249,7 @@ class _MacMusicAppThread(threading.Thread):
|
|||
pass
|
||||
else:
|
||||
babase.pushcall(
|
||||
babase.Call(
|
||||
babase.CallStrict(
|
||||
babase.screenmessage,
|
||||
babase.app.lang.get_resource('playlistNotFoundText')
|
||||
+ ': \''
|
||||
|
|
|
|||
5
dist/ba_data/python/baclassic/osmusic.py
vendored
5
dist/ba_data/python/baclassic/osmusic.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Music playback using OS functionality exposed through the C++ layer."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
|
@ -151,7 +152,7 @@ class _PickFolderSongThread(threading.Thread):
|
|||
).evaluate()
|
||||
)
|
||||
babase.pushcall(
|
||||
babase.Call(self._callback, all_files, None),
|
||||
babase.CallStrict(self._callback, all_files, None),
|
||||
from_other_thread=True,
|
||||
)
|
||||
except Exception as exc:
|
||||
|
|
@ -162,6 +163,6 @@ class _PickFolderSongThread(threading.Thread):
|
|||
except Exception:
|
||||
err_str = '<ENCERR4523>'
|
||||
babase.pushcall(
|
||||
babase.Call(self._callback, self._path, err_str),
|
||||
babase.CallStrict(self._callback, self._path, err_str),
|
||||
from_other_thread=True,
|
||||
)
|
||||
|
|
|
|||
75
dist/ba_data/python/bacommon/analytics.py
vendored
Normal file
75
dist/ba_data/python/bacommon/analytics.py
vendored
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Analytics support."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import assert_never, override, Annotated
|
||||
|
||||
from enum import Enum, unique
|
||||
from dataclasses import dataclass
|
||||
|
||||
from efro.dataclassio import ioprepped, IOMultiType, IOAttrs
|
||||
|
||||
|
||||
class AnalyticsEventTypeID(Enum):
|
||||
"""Type ID for each of our subclasses."""
|
||||
|
||||
CLASSIC = 'c'
|
||||
|
||||
|
||||
class AnalyticsEvent(IOMultiType[AnalyticsEventTypeID]):
|
||||
"""Top level class for our multitype."""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> AnalyticsEventTypeID:
|
||||
# Require child classes to supply this themselves. If we did a
|
||||
# full type registry/lookup here it would require us to import
|
||||
# everything and would prevent lazy loading.
|
||||
raise NotImplementedError()
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type(cls, type_id: AnalyticsEventTypeID) -> type[AnalyticsEvent]:
|
||||
"""Return the subclass for each of our type-ids."""
|
||||
# pylint: disable=cyclic-import
|
||||
|
||||
t = AnalyticsEventTypeID
|
||||
if type_id is t.CLASSIC:
|
||||
return ClassicAnalyticsEvent
|
||||
|
||||
# Important to make sure we provide all types.
|
||||
assert_never(type_id)
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id_storage_name(cls) -> str:
|
||||
return '_t'
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ClassicAnalyticsEvent(AnalyticsEvent):
|
||||
"""Analytics event related to classic."""
|
||||
|
||||
@unique
|
||||
class EventType(Enum):
|
||||
"""Types of classic events."""
|
||||
|
||||
JOIN_PUBLIC_PARTY = 'jpb'
|
||||
JOIN_PRIVATE_PARTY = 'jpr'
|
||||
JOIN_PARTY_BY_ADDRESS = 'ja'
|
||||
JOIN_NEARBY_PARTY = 'jn'
|
||||
START_TEAMS_SESSION = 'st'
|
||||
START_FFA_SESSION = 'sf'
|
||||
START_COOP_SESSION = 'sc'
|
||||
START_TOURNEY_COOP_SESSION = 'stc'
|
||||
|
||||
eventtype: Annotated[EventType, IOAttrs('t')]
|
||||
extra: Annotated[str | None, IOAttrs('e', store_default=False)] = None
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> AnalyticsEventTypeID:
|
||||
return AnalyticsEventTypeID.CLASSIC
|
||||
8
dist/ba_data/python/bacommon/assets.py
vendored
8
dist/ba_data/python/bacommon/assets.py
vendored
|
|
@ -1,6 +1,12 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to cloud based assets."""
|
||||
"""Functionality related to cloud based assets.
|
||||
|
||||
.. warning::
|
||||
|
||||
This is an internal api and subject to change at any time. Do not use
|
||||
it in mod code.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
|
|
|||
43
dist/ba_data/python/bacommon/bacloud.py
vendored
43
dist/ba_data/python/bacommon/bacloud.py
vendored
|
|
@ -1,6 +1,12 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to the bacloud tool."""
|
||||
"""Functionality related to the bacloud tool.
|
||||
|
||||
.. warning::
|
||||
|
||||
This is an internal api and subject to change at any time. Do not use
|
||||
it in mod code.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
|
@ -14,7 +20,7 @@ if TYPE_CHECKING:
|
|||
|
||||
# Version is sent to the master-server with all commands. Can be incremented
|
||||
# if we need to change behavior server-side to go along with client changes.
|
||||
BACLOUD_VERSION = 13
|
||||
BACLOUD_VERSION = 14
|
||||
|
||||
|
||||
def asset_file_cache_path(filehash: str) -> str:
|
||||
|
|
@ -91,12 +97,30 @@ class ResponseData:
|
|||
#: response processing (including error handling) occurs.
|
||||
message: Annotated[str | None, IOAttrs('m', store_default=False)] = None
|
||||
|
||||
#: End arg for message print() call.
|
||||
#: Value for the 'end' arg of the message print() call.
|
||||
message_end: Annotated[str, IOAttrs('m_end', store_default=False)] = '\n'
|
||||
|
||||
#: If present, client should abort with this error message.
|
||||
#: If present, client should print this message before any other
|
||||
#: response processing (including error handling) occurs.
|
||||
message_stderr: Annotated[
|
||||
str | None, IOAttrs('m2', store_default=False)
|
||||
] = None
|
||||
|
||||
#: Value for the 'end' arg of the message print() call.
|
||||
message_stderr_end: Annotated[
|
||||
str, IOAttrs('m2_end', store_default=False)
|
||||
] = '\n'
|
||||
|
||||
#: If present, client should abort with this error message and
|
||||
#: return-code 2.
|
||||
error: Annotated[str | None, IOAttrs('e', store_default=False)] = None
|
||||
|
||||
#: If present for an interactive command, specifies the return code
|
||||
#: for the process. Note that this only applies if error is not set.
|
||||
#: Standard return codes are 0 for success, 1 for a successful run
|
||||
#: but negative result, and 2 for errors.
|
||||
return_code: Annotated[int | None, IOAttrs('r', store_default=False)] = None
|
||||
|
||||
#: How long to wait before proceeding with remaining response (can
|
||||
#: be useful when waiting for server progress in a loop).
|
||||
delay_seconds: Annotated[float, IOAttrs('d', store_default=False)] = 0.0
|
||||
|
|
@ -170,6 +194,17 @@ class ResponseData:
|
|||
#: End arg for end_message print() call.
|
||||
end_message_end: Annotated[str, IOAttrs('eme', store_default=False)] = '\n'
|
||||
|
||||
#: If present, a message that should be printed after all other
|
||||
#: response processing is done.
|
||||
end_message_stderr: Annotated[
|
||||
str | None, IOAttrs('em2', store_default=False)
|
||||
] = None
|
||||
|
||||
#: End arg for end_message print() call.
|
||||
end_message_stderr_end: Annotated[
|
||||
str, IOAttrs('em2e', store_default=False)
|
||||
] = '\n'
|
||||
|
||||
#: If present, this command is run with these args at the end of
|
||||
#: response processing.
|
||||
end_command: Annotated[
|
||||
|
|
|
|||
8
dist/ba_data/python/bacommon/build.py
vendored
8
dist/ba_data/python/bacommon/build.py
vendored
|
|
@ -1,6 +1,12 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to game builds."""
|
||||
"""Functionality related to game builds.
|
||||
|
||||
.. warning::
|
||||
|
||||
This is an internal api and subject to change at any time. Do not use
|
||||
it in mod code.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
|
|
|||
71
dist/ba_data/python/bacommon/classic/__init__.py
vendored
Normal file
71
dist/ba_data/python/bacommon/classic/__init__.py
vendored
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to bombsquad classic.
|
||||
|
||||
.. warning::
|
||||
|
||||
This is an internal api and subject to change at any time. Do not use
|
||||
it in mod code.
|
||||
"""
|
||||
|
||||
from bacommon.classic._account import (
|
||||
ClassicLiveAccountClientData,
|
||||
)
|
||||
from bacommon.classic._classic import (
|
||||
TOKENS1_COUNT,
|
||||
TOKENS2_COUNT,
|
||||
TOKENS3_COUNT,
|
||||
TOKENS4_COUNT,
|
||||
)
|
||||
from bacommon.classic._chest import (
|
||||
ClassicChestAppearance,
|
||||
ClassicChestDisplayItem,
|
||||
)
|
||||
from bacommon.classic._msg import (
|
||||
GetClassicLeaguePresidentButtonInfoMessage,
|
||||
GetClassicLeaguePresidentButtonInfoResponse,
|
||||
ChestInfoMessage,
|
||||
ChestInfoResponse,
|
||||
GetClassicPurchasesMessage,
|
||||
GetClassicPurchasesResponse,
|
||||
GlobalProfileCheckMessage,
|
||||
GlobalProfileCheckResponse,
|
||||
InboxRequestMessage,
|
||||
InboxRequestResponse,
|
||||
LegacyRequest,
|
||||
LegacyResponse,
|
||||
PrivatePartyMessage,
|
||||
PrivatePartyResponse,
|
||||
ScoreSubmitMessage,
|
||||
ScoreSubmitResponse,
|
||||
SendInfoMessage,
|
||||
SendInfoResponse,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
'ChestInfoMessage',
|
||||
'ChestInfoResponse',
|
||||
'ClassicLiveAccountClientData',
|
||||
'ClassicChestAppearance',
|
||||
'ClassicChestDisplayItem',
|
||||
'GetClassicLeaguePresidentButtonInfoMessage',
|
||||
'GetClassicLeaguePresidentButtonInfoResponse',
|
||||
'GetClassicPurchasesMessage',
|
||||
'GetClassicPurchasesResponse',
|
||||
'GlobalProfileCheckMessage',
|
||||
'GlobalProfileCheckResponse',
|
||||
'InboxRequestMessage',
|
||||
'InboxRequestResponse',
|
||||
'LegacyRequest',
|
||||
'LegacyResponse',
|
||||
'PrivatePartyMessage',
|
||||
'PrivatePartyResponse',
|
||||
'ScoreSubmitMessage',
|
||||
'ScoreSubmitResponse',
|
||||
'SendInfoMessage',
|
||||
'SendInfoResponse',
|
||||
'TOKENS1_COUNT',
|
||||
'TOKENS2_COUNT',
|
||||
'TOKENS3_COUNT',
|
||||
'TOKENS4_COUNT',
|
||||
]
|
||||
83
dist/ba_data/python/bacommon/classic/_account.py
vendored
Normal file
83
dist/ba_data/python/bacommon/classic/_account.py
vendored
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""BombSquad specific bits."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass
|
||||
from typing import Annotated
|
||||
|
||||
from efro.dataclassio import ioprepped, IOAttrs
|
||||
from bacommon.classic._chest import ClassicChestAppearance
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ClassicLiveAccountClientData:
|
||||
"""Live account data fed to the client in the bs classic app mode."""
|
||||
|
||||
@dataclass
|
||||
class Chest:
|
||||
"""A lovely chest."""
|
||||
|
||||
appearance: Annotated[
|
||||
ClassicChestAppearance,
|
||||
IOAttrs('a', enum_fallback=ClassicChestAppearance.UNKNOWN),
|
||||
]
|
||||
create_time: Annotated[datetime.datetime, IOAttrs('c')]
|
||||
unlock_time: Annotated[datetime.datetime, IOAttrs('t')]
|
||||
unlock_tokens: Annotated[int, IOAttrs('k')]
|
||||
ad_allow_time: Annotated[datetime.datetime | None, IOAttrs('at')]
|
||||
|
||||
class LeagueType(Enum):
|
||||
"""Type of league we are in."""
|
||||
|
||||
BRONZE = 'b'
|
||||
SILVER = 's'
|
||||
GOLD = 'g'
|
||||
DIAMOND = 'd'
|
||||
|
||||
class Flag(Enum):
|
||||
"""Flags set for our account."""
|
||||
|
||||
ASK_FOR_REVIEW = 'r'
|
||||
|
||||
class StoreStyle(Enum):
|
||||
"""Special looks for the store."""
|
||||
|
||||
NORMAL = 'n'
|
||||
SANTA = 's'
|
||||
|
||||
tickets: Annotated[int, IOAttrs('ti')]
|
||||
|
||||
tokens: Annotated[int, IOAttrs('to')]
|
||||
gold_pass: Annotated[bool, IOAttrs('g')]
|
||||
remove_ads: Annotated[bool, IOAttrs('r')]
|
||||
|
||||
achievements: Annotated[int, IOAttrs('a')]
|
||||
achievements_total: Annotated[int, IOAttrs('at')]
|
||||
|
||||
league_type: Annotated[LeagueType | None, IOAttrs('lt')]
|
||||
league_num: Annotated[int | None, IOAttrs('ln')]
|
||||
league_rank: Annotated[int | None, IOAttrs('lr')]
|
||||
|
||||
level: Annotated[int, IOAttrs('lv')]
|
||||
xp: Annotated[int, IOAttrs('xp')]
|
||||
xpmax: Annotated[int, IOAttrs('xpm')]
|
||||
|
||||
inbox_count: Annotated[int, IOAttrs('ibc')]
|
||||
inbox_count_is_max: Annotated[bool, IOAttrs('ibcm')]
|
||||
inbox_contains_prize: Annotated[bool, IOAttrs('icp')]
|
||||
|
||||
chests: Annotated[dict[str, Chest], IOAttrs('c')]
|
||||
|
||||
# State id of our purchases for builds 22459+.
|
||||
purchases_state: Annotated[str | None, IOAttrs('p')]
|
||||
|
||||
flags: Annotated[set[Flag], IOAttrs('f', soft_default_factory=set)]
|
||||
|
||||
store_style: Annotated[
|
||||
StoreStyle, IOAttrs('s', enum_fallback=StoreStyle.NORMAL)
|
||||
]
|
||||
67
dist/ba_data/python/bacommon/classic/_chest.py
vendored
Normal file
67
dist/ba_data/python/bacommon/classic/_chest.py
vendored
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""BombSquad specific bits."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from typing import assert_never, Annotated, override
|
||||
from dataclasses import dataclass
|
||||
|
||||
from efro.dataclassio import ioprepped, IOAttrs
|
||||
import bacommon.displayitem as ditm
|
||||
|
||||
|
||||
class ClassicChestAppearance(Enum):
|
||||
"""Appearances bombsquad classic chests can have."""
|
||||
|
||||
UNKNOWN = 'u'
|
||||
DEFAULT = 'd'
|
||||
L1 = 'l1'
|
||||
L2 = 'l2'
|
||||
L3 = 'l3'
|
||||
L4 = 'l4'
|
||||
L5 = 'l5'
|
||||
L6 = 'l6'
|
||||
|
||||
@property
|
||||
def pretty_name(self) -> str:
|
||||
"""Pretty name for the chest in English."""
|
||||
# pylint: disable=too-many-return-statements
|
||||
cls = ClassicChestAppearance
|
||||
|
||||
if self is cls.UNKNOWN:
|
||||
return 'Unknown Chest'
|
||||
if self is cls.DEFAULT:
|
||||
return 'Chest'
|
||||
if self is cls.L1:
|
||||
return 'L1 Chest'
|
||||
if self is cls.L2:
|
||||
return 'L2 Chest'
|
||||
if self is cls.L3:
|
||||
return 'L3 Chest'
|
||||
if self is cls.L4:
|
||||
return 'L4 Chest'
|
||||
if self is cls.L5:
|
||||
return 'L5 Chest'
|
||||
if self is cls.L6:
|
||||
return 'L6 Chest'
|
||||
|
||||
assert_never(self)
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ClassicChestDisplayItem(ditm.Item):
|
||||
"""Display a chest."""
|
||||
|
||||
appearance: Annotated[ClassicChestAppearance, IOAttrs('a')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> ditm.ItemTypeID:
|
||||
return ditm.ItemTypeID.CHEST
|
||||
|
||||
@override
|
||||
def get_description(self) -> tuple[str, list[tuple[str, str]]]:
|
||||
return self.appearance.pretty_name, []
|
||||
9
dist/ba_data/python/bacommon/classic/_classic.py
vendored
Normal file
9
dist/ba_data/python/bacommon/classic/_classic.py
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""BombSquad specific bits."""
|
||||
|
||||
# Token counts for our various packs.
|
||||
TOKENS1_COUNT = 50
|
||||
TOKENS2_COUNT = 500
|
||||
TOKENS3_COUNT = 1200
|
||||
TOKENS4_COUNT = 2600
|
||||
28
dist/ba_data/python/bacommon/classic/_displayitem.py
vendored
Normal file
28
dist/ba_data/python/bacommon/classic/_displayitem.py
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Display-item bits of classic."""
|
||||
|
||||
# from __future__ import annotations
|
||||
|
||||
# from enum import Enum
|
||||
# from typing import assert_never, Annotated, override
|
||||
# from dataclasses import dataclass
|
||||
|
||||
# from efro.dataclassio import ioprepped, IOAttrs
|
||||
# import bacommon.displayitem as ditm
|
||||
|
||||
# @ioprepped
|
||||
# @dataclass
|
||||
# class ClassicCharacterDisplayItem(ditm.Item):
|
||||
# """Display a character."""
|
||||
|
||||
# : Annotated[ClassicChestAppearance, IOAttrs('a')]
|
||||
|
||||
# @override
|
||||
# @classmethod
|
||||
# def get_type_id(cls) -> ditm.ItemTypeID:
|
||||
# return ditm.ItemTypeID.CHEST
|
||||
|
||||
# @override
|
||||
# def get_description(self) -> tuple[str, list[tuple[str, str]]]:
|
||||
# return self.appearance.pretty_name, []
|
||||
249
dist/ba_data/python/bacommon/classic/_msg.py
vendored
Normal file
249
dist/ba_data/python/bacommon/classic/_msg.py
vendored
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""BombSquad specific bits."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Annotated, override
|
||||
|
||||
from efro.dataclassio import ioprepped, IOAttrs
|
||||
from efro.message import Message, Response
|
||||
|
||||
import bacommon.displayitem as ditm
|
||||
import bacommon.clouddialog as cdlg
|
||||
import bacommon.clienteffect as clfx
|
||||
from bacommon.classic._chest import ClassicChestAppearance
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class GetClassicLeaguePresidentButtonInfoMessage(Message):
|
||||
"""Curious who is president of my league?.."""
|
||||
|
||||
season: Annotated[str | None, IOAttrs('s')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [GetClassicLeaguePresidentButtonInfoResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class GetClassicLeaguePresidentButtonInfoResponse(Response):
|
||||
"""Here's that info about the president you asked for boss."""
|
||||
|
||||
# Lstr for the name shown on the button.
|
||||
name: Annotated[str | None, IOAttrs('n')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class GetClassicPurchasesMessage(Message):
|
||||
"""Asking for current account's classic purchases."""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [GetClassicPurchasesResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class GetClassicPurchasesResponse(Response):
|
||||
"""Here's those classic purchases ya asked for boss."""
|
||||
|
||||
purchases: Annotated[set[str], IOAttrs('p')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class GlobalProfileCheckMessage(Message):
|
||||
"""Is this global profile name available?"""
|
||||
|
||||
name: Annotated[str, IOAttrs('n')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [GlobalProfileCheckResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class GlobalProfileCheckResponse(Response):
|
||||
"""Here's that profile check ya asked for boss."""
|
||||
|
||||
available: Annotated[bool, IOAttrs('a')]
|
||||
ticket_cost: Annotated[int, IOAttrs('tc')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class InboxRequestMessage(Message):
|
||||
"""Message requesting our inbox."""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [InboxRequestResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class InboxRequestResponse(Response):
|
||||
"""Here's that inbox contents you asked for, boss."""
|
||||
|
||||
wrappers: Annotated[list[cdlg.Wrapper], IOAttrs('w')]
|
||||
|
||||
# Printable error if something goes wrong.
|
||||
error: Annotated[str | None, IOAttrs('e')] = None
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class LegacyRequest(Message):
|
||||
"""A generic request for the legacy master server."""
|
||||
|
||||
request: Annotated[str, IOAttrs('r')]
|
||||
request_type: Annotated[str, IOAttrs('t')]
|
||||
user_agent_string: Annotated[str, IOAttrs('u')]
|
||||
data: Annotated[str, IOAttrs('d')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [LegacyResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class LegacyResponse(Response):
|
||||
"""Response for generic legacy request."""
|
||||
|
||||
data: Annotated[str | None, IOAttrs('d')]
|
||||
zipped: Annotated[bool, IOAttrs('z')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ChestInfoMessage(Message):
|
||||
"""Request info about a chest."""
|
||||
|
||||
chest_id: Annotated[str, IOAttrs('i')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [ChestInfoResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ChestInfoResponse(Response):
|
||||
"""Here's that chest info you asked for, boss."""
|
||||
|
||||
@dataclass
|
||||
class Chest:
|
||||
"""A lovely chest."""
|
||||
|
||||
@dataclass
|
||||
class PrizeSet:
|
||||
"""A possible set of prizes for this chest."""
|
||||
|
||||
weight: Annotated[float, IOAttrs('w')]
|
||||
contents: Annotated[list[ditm.Wrapper], IOAttrs('c')]
|
||||
|
||||
appearance: Annotated[
|
||||
ClassicChestAppearance,
|
||||
IOAttrs('a', enum_fallback=ClassicChestAppearance.UNKNOWN),
|
||||
]
|
||||
|
||||
# How much it costs to unlock *now*.
|
||||
unlock_tokens: Annotated[int, IOAttrs('tk')]
|
||||
|
||||
# When it unlocks on its own.
|
||||
unlock_time: Annotated[datetime.datetime, IOAttrs('t')]
|
||||
|
||||
# Possible prizes we contain.
|
||||
prizesets: Annotated[list[PrizeSet], IOAttrs('p')]
|
||||
|
||||
# Are ads allowed now?
|
||||
ad_allow: Annotated[bool, IOAttrs('aa')]
|
||||
|
||||
chest: Annotated[Chest | None, IOAttrs('c')]
|
||||
user_tokens: Annotated[int | None, IOAttrs('t')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class PrivatePartyMessage(Message):
|
||||
"""Message asking about info we need for private-party UI."""
|
||||
|
||||
need_datacode: Annotated[bool, IOAttrs('d')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [PrivatePartyResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class PrivatePartyResponse(Response):
|
||||
"""Here's that private party UI info you asked for, boss."""
|
||||
|
||||
success: Annotated[bool, IOAttrs('s')]
|
||||
tokens: Annotated[int, IOAttrs('t')]
|
||||
gold_pass: Annotated[bool, IOAttrs('g')]
|
||||
datacode: Annotated[str | None, IOAttrs('d')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ScoreSubmitMessage(Message):
|
||||
"""Let the server know we got some score in something."""
|
||||
|
||||
score_token: Annotated[str, IOAttrs('t')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [ScoreSubmitResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ScoreSubmitResponse(Response):
|
||||
"""Did something to that inbox entry, boss."""
|
||||
|
||||
# Things we should show on our end.
|
||||
effects: Annotated[list[clfx.Effect], IOAttrs('fx')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class SendInfoMessage(Message):
|
||||
"""User is using the send-info function."""
|
||||
|
||||
description: Annotated[str, IOAttrs('c')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [SendInfoResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class SendInfoResponse(Response):
|
||||
"""Response to sending info to the server."""
|
||||
|
||||
handled: Annotated[bool, IOAttrs('v')]
|
||||
message: Annotated[str | None, IOAttrs('m', store_default=False)] = None
|
||||
effects: Annotated[list[clfx.Effect], IOAttrs('e', store_default=False)] = (
|
||||
field(default_factory=list)
|
||||
)
|
||||
legacy_code: Annotated[str | None, IOAttrs('l', store_default=False)] = None
|
||||
224
dist/ba_data/python/bacommon/clienteffect.py
vendored
Normal file
224
dist/ba_data/python/bacommon/clienteffect.py
vendored
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""ClientEffect related functionality.
|
||||
|
||||
.. warning::
|
||||
|
||||
This is an internal api and subject to change at any time. Do not use
|
||||
it in mod code.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Annotated, override, assert_never
|
||||
|
||||
from efro.dataclassio import ioprepped, IOAttrs, IOMultiType
|
||||
|
||||
|
||||
class EffectTypeID(Enum):
|
||||
"""Type ID for each of our subclasses."""
|
||||
|
||||
UNKNOWN = 'u'
|
||||
LEGACY_SCREEN_MESSAGE = 'm'
|
||||
SCREEN_MESSAGE = 'sm'
|
||||
SOUND = 's'
|
||||
DELAY = 'd'
|
||||
CHEST_WAIT_TIME_ANIMATION = 't'
|
||||
TICKETS_ANIMATION = 'ta'
|
||||
TOKENS_ANIMATION = 'toa'
|
||||
|
||||
|
||||
class Effect(IOMultiType[EffectTypeID]):
|
||||
"""Something that can happen on the client.
|
||||
|
||||
This can include screen messages, sounds, visual effects, etc.
|
||||
"""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> EffectTypeID:
|
||||
# Require child classes to supply this themselves. If we did a
|
||||
# full type registry/lookup here it would require us to import
|
||||
# everything and would prevent lazy loading.
|
||||
raise NotImplementedError()
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type(cls, type_id: EffectTypeID) -> type[Effect]:
|
||||
"""Return the subclass for each of our type-ids."""
|
||||
# pylint: disable=cyclic-import
|
||||
# pylint: disable=too-many-return-statements
|
||||
|
||||
t = EffectTypeID
|
||||
if type_id is t.UNKNOWN:
|
||||
return Unknown
|
||||
if type_id is t.LEGACY_SCREEN_MESSAGE:
|
||||
return LegacyScreenMessage
|
||||
if type_id is t.SCREEN_MESSAGE:
|
||||
return ScreenMessage
|
||||
if type_id is t.SOUND:
|
||||
return PlaySound
|
||||
if type_id is t.DELAY:
|
||||
return Delay
|
||||
if type_id is t.CHEST_WAIT_TIME_ANIMATION:
|
||||
return ChestWaitTimeAnimation
|
||||
if type_id is t.TICKETS_ANIMATION:
|
||||
return TicketsAnimation
|
||||
if type_id is t.TOKENS_ANIMATION:
|
||||
return TokensAnimation
|
||||
|
||||
# Important to make sure we provide all types.
|
||||
assert_never(type_id)
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_unknown_type_fallback(cls) -> Effect:
|
||||
# If we encounter some future message type we don't know
|
||||
# anything about, drop in a placeholder.
|
||||
return Unknown()
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class Unknown(Effect):
|
||||
"""Fallback substitute for types we don't recognize."""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> EffectTypeID:
|
||||
return EffectTypeID.UNKNOWN
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class LegacyScreenMessage(Effect):
|
||||
"""Display a screen-message (Legacy version).
|
||||
|
||||
This will be processed as an Lstr with translation category
|
||||
'serverResponses'.
|
||||
|
||||
When possible, migrate to using :class:`ScreenMessage`.
|
||||
"""
|
||||
|
||||
message: Annotated[str, IOAttrs('m')]
|
||||
subs: Annotated[list[str], IOAttrs('s', store_default=False)] = field(
|
||||
default_factory=list
|
||||
)
|
||||
color: Annotated[
|
||||
tuple[float, float, float], IOAttrs('c', store_default=False)
|
||||
] = (1.0, 1.0, 1.0)
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> EffectTypeID:
|
||||
return EffectTypeID.LEGACY_SCREEN_MESSAGE
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ScreenMessage(Effect):
|
||||
"""Display a screen-message.
|
||||
|
||||
Supported on engine build 22606 or newer.
|
||||
|
||||
This version does no translation by default (expecting translation
|
||||
to happen server-side). Pass a Lstr json string and set is_lstr=True
|
||||
for client-side translation.
|
||||
"""
|
||||
|
||||
message: Annotated[str, IOAttrs('m')]
|
||||
color: Annotated[
|
||||
tuple[float, float, float], IOAttrs('c', store_default=False)
|
||||
] = (1.0, 1.0, 1.0)
|
||||
is_lstr: Annotated[bool, IOAttrs('l', store_default=False)] = False
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> EffectTypeID:
|
||||
return EffectTypeID.SCREEN_MESSAGE
|
||||
|
||||
|
||||
class Sound(Enum):
|
||||
"""Sounds that can be played."""
|
||||
|
||||
UNKNOWN = 'u'
|
||||
CASH_REGISTER = 'c'
|
||||
ERROR = 'e'
|
||||
POWER_DOWN = 'p'
|
||||
GUN_COCKING = 'g'
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class PlaySound(Effect):
|
||||
"""Play a sound."""
|
||||
|
||||
sound: Annotated[Sound, IOAttrs('s', enum_fallback=Sound.UNKNOWN)]
|
||||
volume: Annotated[float, IOAttrs('v', store_default=False)] = 1.0
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> EffectTypeID:
|
||||
return EffectTypeID.SOUND
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ChestWaitTimeAnimation(Effect):
|
||||
"""Animate chest wait time changing."""
|
||||
|
||||
chestid: Annotated[str, IOAttrs('c')]
|
||||
duration: Annotated[float, IOAttrs('u')]
|
||||
startvalue: Annotated[datetime.datetime, IOAttrs('o')]
|
||||
endvalue: Annotated[datetime.datetime, IOAttrs('n')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> EffectTypeID:
|
||||
return EffectTypeID.CHEST_WAIT_TIME_ANIMATION
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class TicketsAnimation(Effect):
|
||||
"""Animate tickets count."""
|
||||
|
||||
duration: Annotated[float, IOAttrs('u')]
|
||||
startvalue: Annotated[int, IOAttrs('s')]
|
||||
endvalue: Annotated[int, IOAttrs('e')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> EffectTypeID:
|
||||
return EffectTypeID.TICKETS_ANIMATION
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class TokensAnimation(Effect):
|
||||
"""Animate tokens count."""
|
||||
|
||||
duration: Annotated[float, IOAttrs('u')]
|
||||
startvalue: Annotated[int, IOAttrs('s')]
|
||||
endvalue: Annotated[int, IOAttrs('e')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> EffectTypeID:
|
||||
return EffectTypeID.TOKENS_ANIMATION
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class Delay(Effect):
|
||||
"""Delay effect processing."""
|
||||
|
||||
seconds: Annotated[float, IOAttrs('s')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> EffectTypeID:
|
||||
return EffectTypeID.DELAY
|
||||
107
dist/ba_data/python/bacommon/cloud.py
vendored
107
dist/ba_data/python/bacommon/cloud.py
vendored
|
|
@ -1,6 +1,12 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to cloud functionality."""
|
||||
"""Cloud related functionality.
|
||||
|
||||
.. warning::
|
||||
|
||||
This is an internal api and subject to change at any time. Do not use
|
||||
it in mod code.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
|
@ -10,9 +16,13 @@ from typing import TYPE_CHECKING, Annotated, override
|
|||
|
||||
from efro.message import Message, Response
|
||||
from efro.dataclassio import ioprepped, IOAttrs
|
||||
from bacommon.analytics import AnalyticsEvent
|
||||
from bacommon.securedata import SecureDataChecker
|
||||
from bacommon.transfer import DirectoryManifest
|
||||
from bacommon.login import LoginType
|
||||
from bacommon.docui import DocUIRequest, DocUIResponse
|
||||
import bacommon.displayitem as ditm
|
||||
import bacommon.clienteffect as clfx
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
|
@ -357,3 +367,98 @@ class CloudValsResponse(Response):
|
|||
"""Here's them cloud vals ya asked for, boss."""
|
||||
|
||||
vals: Annotated[CloudVals, IOAttrs('v')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ChestActionMessage(Message):
|
||||
"""Request action about a chest."""
|
||||
|
||||
class Action(Enum):
|
||||
"""Types of actions we can request."""
|
||||
|
||||
# Unlocking (for free or with tokens).
|
||||
UNLOCK = 'u'
|
||||
|
||||
# Watched an ad to reduce wait.
|
||||
AD = 'ad'
|
||||
|
||||
action: Annotated[Action, IOAttrs('a')]
|
||||
|
||||
# Tokens we are paying (only applies to unlock).
|
||||
token_payment: Annotated[int, IOAttrs('t')]
|
||||
|
||||
chest_id: Annotated[str, IOAttrs('i')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [ChestActionResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ChestActionResponse(Response):
|
||||
"""Here's the results of that action you asked for, boss."""
|
||||
|
||||
# Tokens that were actually charged.
|
||||
tokens_charged: Annotated[int, IOAttrs('t')] = 0
|
||||
|
||||
# If present, signifies the chest has been opened and we should show
|
||||
# the user this stuff that was in it.
|
||||
contents: Annotated[list[ditm.Wrapper] | None, IOAttrs('c')] = None
|
||||
|
||||
# If contents are present, which of the chest's prize-sets they
|
||||
# represent.
|
||||
prizeindex: Annotated[int, IOAttrs('i')] = 0
|
||||
|
||||
# Printable error if something goes wrong.
|
||||
error: Annotated[str | None, IOAttrs('e')] = None
|
||||
|
||||
# Printable warning. Shown in orange with an error sound. Does not
|
||||
# mean the action failed; only that there's something to tell the
|
||||
# users such as 'It looks like you are faking ad views; stop it or
|
||||
# you won't have ad options anymore.'
|
||||
warning: Annotated[str | None, IOAttrs('w', store_default=False)] = None
|
||||
|
||||
# Printable success message. Shown in green with a cash-register
|
||||
# sound. Can be used for things like successful wait reductions via
|
||||
# ad views. Used in builds earlier than 22311; can remove once
|
||||
# 22311+ is ubiquitous.
|
||||
success_msg: Annotated[str | None, IOAttrs('s', store_default=False)] = None
|
||||
|
||||
# Effects to show on the client. Replaces warning and success_msg in
|
||||
# build 22311 or newer.
|
||||
effects: Annotated[
|
||||
list[clfx.Effect], IOAttrs('fx', store_default=False)
|
||||
] = field(default_factory=list)
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class FulfillDocUIRequest(Message):
|
||||
"""Can a fella get a doc-ui round here?"""
|
||||
|
||||
request: Annotated[DocUIRequest, IOAttrs('r')]
|
||||
domain: Annotated[str, IOAttrs('d')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [FulfillDocUIResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class FulfillDocUIResponse(Response):
|
||||
"""Here's that doc-ui you asked for, boss."""
|
||||
|
||||
response: Annotated[DocUIResponse, IOAttrs('r')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class AnalyticsEventMessage(Message):
|
||||
"""Have a nice analytics event!"""
|
||||
|
||||
event: Annotated[AnalyticsEvent, IOAttrs('e')]
|
||||
|
|
|
|||
29
dist/ba_data/python/bacommon/clouddialog/__init__.py
vendored
Normal file
29
dist/ba_data/python/bacommon/clouddialog/__init__.py
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to cloud-dialogs.
|
||||
|
||||
.. warning::
|
||||
|
||||
This is an internal api and subject to change at any time. Do not use
|
||||
it in mod code.
|
||||
"""
|
||||
|
||||
from bacommon.clouddialog._clouddialog import (
|
||||
CloudDialogTypeID,
|
||||
CloudDialog,
|
||||
Unknown,
|
||||
Wrapper,
|
||||
Action,
|
||||
ActionMessage,
|
||||
ActionResponse,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
'CloudDialogTypeID',
|
||||
'CloudDialog',
|
||||
'Unknown',
|
||||
'Wrapper',
|
||||
'Action',
|
||||
'ActionMessage',
|
||||
'ActionResponse',
|
||||
]
|
||||
142
dist/ba_data/python/bacommon/clouddialog/_clouddialog.py
vendored
Normal file
142
dist/ba_data/python/bacommon/clouddialog/_clouddialog.py
vendored
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Simple cloud-defined UIs for things like notifications.
|
||||
|
||||
.. warning::
|
||||
|
||||
This is an internal api and subject to change at any time. Do not use
|
||||
it in mod code.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass
|
||||
from typing import Annotated, override, assert_never
|
||||
|
||||
from efro.dataclassio import ioprepped, IOAttrs, IOMultiType
|
||||
from efro.message import Message, Response
|
||||
|
||||
import bacommon.clienteffect as clfx
|
||||
|
||||
|
||||
class CloudDialogTypeID(Enum):
|
||||
"""Type ID for each of our subclasses."""
|
||||
|
||||
UNKNOWN = 'u'
|
||||
BASIC = 'b'
|
||||
|
||||
|
||||
class CloudDialog(IOMultiType[CloudDialogTypeID]):
|
||||
"""Small self-contained ui bit provided by the cloud.
|
||||
|
||||
These take care of updating and/or dismissing themselves based on
|
||||
user input. Useful for things such as inbox messages. For more
|
||||
complex UI construction, look at :mod:`bacommon.docui`.
|
||||
"""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> CloudDialogTypeID:
|
||||
# Require child classes to supply this themselves. If we did a
|
||||
# full type registry/lookup here it would require us to import
|
||||
# everything and would prevent lazy loading.
|
||||
raise NotImplementedError()
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type(cls, type_id: CloudDialogTypeID) -> type[CloudDialog]:
|
||||
"""Return the subclass for each of our type-ids."""
|
||||
# pylint: disable=cyclic-import
|
||||
|
||||
t = CloudDialogTypeID
|
||||
|
||||
if type_id is t.UNKNOWN:
|
||||
return Unknown
|
||||
|
||||
if type_id is t.BASIC:
|
||||
from bacommon.clouddialog.basic import BasicCloudDialog
|
||||
|
||||
return BasicCloudDialog
|
||||
|
||||
# Make sure we provide all types.
|
||||
assert_never(type_id)
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_unknown_type_fallback(cls) -> CloudDialog:
|
||||
# If we encounter some future message type we don't know
|
||||
# anything about, drop in a placeholder.
|
||||
return Unknown()
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class Unknown(CloudDialog):
|
||||
"""Fallback type for unrecognized entries."""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> CloudDialogTypeID:
|
||||
return CloudDialogTypeID.UNKNOWN
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class Wrapper:
|
||||
"""Wrapper for a CloudDialog and its common data."""
|
||||
|
||||
id: Annotated[str, IOAttrs('i')]
|
||||
createtime: Annotated[datetime.datetime, IOAttrs('c')]
|
||||
ui: Annotated[CloudDialog, IOAttrs('e')]
|
||||
|
||||
|
||||
class Action(Enum):
|
||||
"""Types of actions we can run."""
|
||||
|
||||
BUTTON_PRESS_POSITIVE = 'p'
|
||||
BUTTON_PRESS_NEGATIVE = 'n'
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ActionMessage(Message):
|
||||
"""Do something to a client ui."""
|
||||
|
||||
id: Annotated[str, IOAttrs('i')]
|
||||
action: Annotated[Action, IOAttrs('a')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [ActionResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ActionResponse(Response):
|
||||
"""Did something to that inbox entry, boss."""
|
||||
|
||||
class ErrorType(Enum):
|
||||
"""Types of errors that may have occurred."""
|
||||
|
||||
# Probably a future error type we don't recognize.
|
||||
UNKNOWN = 'u'
|
||||
|
||||
# Something went wrong on the server, but specifics are not
|
||||
# relevant.
|
||||
INTERNAL = 'i'
|
||||
|
||||
# The entry expired on the server. In various cases such as 'ok'
|
||||
# buttons this can generally be ignored.
|
||||
EXPIRED = 'e'
|
||||
|
||||
error_type: Annotated[
|
||||
ErrorType | None, IOAttrs('et', enum_fallback=ErrorType.UNKNOWN)
|
||||
]
|
||||
|
||||
# User facing error message in the case of errors.
|
||||
error_message: Annotated[str | None, IOAttrs('em')]
|
||||
|
||||
effects: Annotated[list[clfx.Effect], IOAttrs('fx')]
|
||||
233
dist/ba_data/python/bacommon/clouddialog/basic.py
vendored
Normal file
233
dist/ba_data/python/bacommon/clouddialog/basic.py
vendored
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Basic cloud-dialog."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Annotated, override, assert_never
|
||||
|
||||
from efro.dataclassio import ioprepped, IOAttrs, IOMultiType
|
||||
|
||||
import bacommon.displayitem as ditm
|
||||
from bacommon.clouddialog._clouddialog import CloudDialog, CloudDialogTypeID
|
||||
|
||||
|
||||
class ComponentTypeID(Enum):
|
||||
"""Type ID for each of our subclasses."""
|
||||
|
||||
UNKNOWN = 'u'
|
||||
TEXT = 't'
|
||||
LINK = 'l'
|
||||
BS_CLASSIC_TOURNEY_RESULT = 'ct'
|
||||
DISPLAY_ITEMS = 'di'
|
||||
EXPIRE_TIME = 'd'
|
||||
|
||||
|
||||
class Component(IOMultiType[ComponentTypeID]):
|
||||
"""Top level class for our multitype."""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> ComponentTypeID:
|
||||
# Require child classes to supply this themselves. If we did a
|
||||
# full type registry/lookup here it would require us to import
|
||||
# everything and would prevent lazy loading.
|
||||
raise NotImplementedError()
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type(cls, type_id: ComponentTypeID) -> type[Component]:
|
||||
"""Return the subclass for each of our type-ids."""
|
||||
# pylint: disable=cyclic-import
|
||||
|
||||
t = ComponentTypeID
|
||||
if type_id is t.UNKNOWN:
|
||||
return Unknown
|
||||
if type_id is t.TEXT:
|
||||
return Text
|
||||
if type_id is t.LINK:
|
||||
return Link
|
||||
if type_id is t.BS_CLASSIC_TOURNEY_RESULT:
|
||||
return ClassicTourneyResult
|
||||
if type_id is t.DISPLAY_ITEMS:
|
||||
return DisplayItems
|
||||
if type_id is t.EXPIRE_TIME:
|
||||
return ExpireTime
|
||||
|
||||
# Important to make sure we provide all types.
|
||||
assert_never(type_id)
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_unknown_type_fallback(cls) -> Component:
|
||||
# If we encounter some future message type we don't know
|
||||
# anything about, drop in a placeholder.
|
||||
return Unknown()
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class Unknown(Component):
|
||||
"""An unknown basic client component type.
|
||||
|
||||
In practice these should never show up since the master-server
|
||||
generates these on the fly for the client and so should not send
|
||||
clients one they can't digest.
|
||||
"""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> ComponentTypeID:
|
||||
return ComponentTypeID.UNKNOWN
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class Text(Component):
|
||||
"""Show some text in the inbox message."""
|
||||
|
||||
text: Annotated[str, IOAttrs('t')]
|
||||
subs: Annotated[list[str], IOAttrs('s', store_default=False)] = field(
|
||||
default_factory=list
|
||||
)
|
||||
scale: Annotated[float, IOAttrs('sc', store_default=False)] = 1.0
|
||||
color: Annotated[
|
||||
tuple[float, float, float, float], IOAttrs('c', store_default=False)
|
||||
] = (1.0, 1.0, 1.0, 1.0)
|
||||
spacing_top: Annotated[float, IOAttrs('st', store_default=False)] = 0.0
|
||||
spacing_bottom: Annotated[float, IOAttrs('sb', store_default=False)] = 0.0
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> ComponentTypeID:
|
||||
return ComponentTypeID.TEXT
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class Link(Component):
|
||||
"""Show a link in the inbox message."""
|
||||
|
||||
url: Annotated[str, IOAttrs('u')]
|
||||
label: Annotated[str, IOAttrs('l')]
|
||||
subs: Annotated[list[str], IOAttrs('s', store_default=False)] = field(
|
||||
default_factory=list
|
||||
)
|
||||
spacing_top: Annotated[float, IOAttrs('st', store_default=False)] = 0.0
|
||||
spacing_bottom: Annotated[float, IOAttrs('sb', store_default=False)] = 0.0
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> ComponentTypeID:
|
||||
return ComponentTypeID.LINK
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ClassicTourneyResult(Component):
|
||||
"""Show info about a classic tourney."""
|
||||
|
||||
tournament_id: Annotated[str, IOAttrs('t')]
|
||||
game: Annotated[str, IOAttrs('g')]
|
||||
players: Annotated[int, IOAttrs('p')]
|
||||
rank: Annotated[int, IOAttrs('r')]
|
||||
trophy: Annotated[str | None, IOAttrs('tr')]
|
||||
prizes: Annotated[list[ditm.Wrapper], IOAttrs('pr')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> ComponentTypeID:
|
||||
return ComponentTypeID.BS_CLASSIC_TOURNEY_RESULT
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class DisplayItems(Component):
|
||||
"""Show some display-items."""
|
||||
|
||||
items: Annotated[list[ditm.Wrapper], IOAttrs('d')]
|
||||
width: Annotated[float, IOAttrs('w')] = 100.0
|
||||
spacing_top: Annotated[float, IOAttrs('st', store_default=False)] = 0.0
|
||||
spacing_bottom: Annotated[float, IOAttrs('sb', store_default=False)] = 0.0
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> ComponentTypeID:
|
||||
return ComponentTypeID.DISPLAY_ITEMS
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ExpireTime(Component):
|
||||
"""Show expire-time."""
|
||||
|
||||
time: Annotated[datetime.datetime, IOAttrs('d')]
|
||||
spacing_top: Annotated[float, IOAttrs('st', store_default=False)] = 0.0
|
||||
spacing_bottom: Annotated[float, IOAttrs('sb', store_default=False)] = 0.0
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> ComponentTypeID:
|
||||
return ComponentTypeID.EXPIRE_TIME
|
||||
|
||||
|
||||
class ButtonLabel(Enum):
|
||||
"""Distinct button labels we support."""
|
||||
|
||||
UNKNOWN = 'u'
|
||||
OK = 'o'
|
||||
APPLY = 'a'
|
||||
CANCEL = 'c'
|
||||
ACCEPT = 'ac'
|
||||
DECLINE = 'dn'
|
||||
IGNORE = 'ig'
|
||||
CLAIM = 'cl'
|
||||
DISCARD = 'd'
|
||||
|
||||
|
||||
class InteractionStyle(Enum):
|
||||
"""Overall interaction styles we support."""
|
||||
|
||||
UNKNOWN = 'u'
|
||||
BUTTON_POSITIVE = 'p'
|
||||
BUTTON_POSITIVE_NEGATIVE = 'pn'
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class BasicCloudDialog(CloudDialog):
|
||||
"""A basic UI for the client."""
|
||||
|
||||
components: Annotated[list[Component], IOAttrs('s')]
|
||||
|
||||
interaction_style: Annotated[
|
||||
InteractionStyle, IOAttrs('i', enum_fallback=InteractionStyle.UNKNOWN)
|
||||
] = InteractionStyle.BUTTON_POSITIVE
|
||||
|
||||
button_label_positive: Annotated[
|
||||
ButtonLabel, IOAttrs('p', enum_fallback=ButtonLabel.UNKNOWN)
|
||||
] = ButtonLabel.OK
|
||||
|
||||
button_label_negative: Annotated[
|
||||
ButtonLabel, IOAttrs('n', enum_fallback=ButtonLabel.UNKNOWN)
|
||||
] = ButtonLabel.CANCEL
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> CloudDialogTypeID:
|
||||
return CloudDialogTypeID.BASIC
|
||||
|
||||
def contains_unknown_elements(self) -> bool:
|
||||
"""Whether something within us is an unknown type or enum."""
|
||||
return (
|
||||
self.interaction_style is InteractionStyle.UNKNOWN
|
||||
or self.button_label_positive is ButtonLabel.UNKNOWN
|
||||
or self.button_label_negative is ButtonLabel.UNKNOWN
|
||||
or any(
|
||||
c.get_type_id() is ComponentTypeID.UNKNOWN
|
||||
for c in self.components
|
||||
)
|
||||
)
|
||||
198
dist/ba_data/python/bacommon/displayitem.py
vendored
Normal file
198
dist/ba_data/python/bacommon/displayitem.py
vendored
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality for displaying currencies, prizes, owned items, etc.
|
||||
|
||||
.. warning::
|
||||
|
||||
This is an internal api and subject to change at any time. Do not use
|
||||
it in mod code.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass
|
||||
from typing import Annotated, override, assert_never
|
||||
|
||||
from efro.util import pairs_to_flat
|
||||
from efro.dataclassio import ioprepped, IOAttrs, IOMultiType
|
||||
|
||||
|
||||
class ItemTypeID(Enum):
|
||||
"""Type ID for each of our subclasses."""
|
||||
|
||||
UNKNOWN = 'u'
|
||||
TICKETS = 't'
|
||||
TICKETS_PURPLE = 'tp'
|
||||
TOKENS = 'k'
|
||||
TEST = 's'
|
||||
CHEST = 'c'
|
||||
|
||||
|
||||
class Item(IOMultiType[ItemTypeID]):
|
||||
"""Some amount of something that can be shown or described.
|
||||
|
||||
Used to depict chest contents, inventory, rewards, etc.
|
||||
"""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> ItemTypeID:
|
||||
# Require child classes to supply this themselves. If we did a
|
||||
# full type registry/lookup here it would require us to import
|
||||
# everything and would prevent lazy loading.
|
||||
raise NotImplementedError()
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type(cls, type_id: ItemTypeID) -> type[Item]:
|
||||
"""Return the subclass for each of our type-ids."""
|
||||
# pylint: disable=cyclic-import
|
||||
|
||||
t = ItemTypeID
|
||||
if type_id is t.UNKNOWN:
|
||||
return Unknown
|
||||
if type_id is t.TICKETS:
|
||||
return Tickets
|
||||
if type_id is t.TICKETS_PURPLE:
|
||||
return PurpleTickets
|
||||
if type_id is t.TOKENS:
|
||||
return Tokens
|
||||
if type_id is t.TEST:
|
||||
return Test
|
||||
if type_id is t.CHEST:
|
||||
from bacommon.classic._chest import ClassicChestDisplayItem
|
||||
|
||||
return ClassicChestDisplayItem
|
||||
|
||||
# Important to make sure we provide all types.
|
||||
assert_never(type_id)
|
||||
|
||||
def get_description(self) -> tuple[str, list[tuple[str, str]]]:
|
||||
"""Return a string description and subs for the item.
|
||||
|
||||
Will be translated on the client using the 'displayItemNames'
|
||||
Lstr category.
|
||||
|
||||
These decriptions are baked into the display-item wrapper and
|
||||
should be accessed from there when available. This allows
|
||||
clients to give descriptions even for newer display item types
|
||||
they don't recognize.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
# Implement fallbacks so client can digest item lists even if they
|
||||
# contain unrecognized stuff. The wrapper contains basic
|
||||
# baked down info that they can still use in such cases.
|
||||
@override
|
||||
@classmethod
|
||||
def get_unknown_type_fallback(cls) -> Item:
|
||||
return Unknown()
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class Unknown(Item):
|
||||
"""Something we don't know how to display."""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> ItemTypeID:
|
||||
return ItemTypeID.UNKNOWN
|
||||
|
||||
@override
|
||||
def get_description(self) -> tuple[str, list[tuple[str, str]]]:
|
||||
import logging
|
||||
|
||||
# Make noise but don't break.
|
||||
logging.exception(
|
||||
'Unknown.get_description() should never be called.'
|
||||
' Always access descriptions on the display-item wrapper.'
|
||||
)
|
||||
return 'Unknown', []
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class Tickets(Item):
|
||||
"""Some amount of tickets."""
|
||||
|
||||
count: Annotated[int, IOAttrs('c')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> ItemTypeID:
|
||||
return ItemTypeID.TICKETS
|
||||
|
||||
@override
|
||||
def get_description(self) -> tuple[str, list[tuple[str, str]]]:
|
||||
return '${C} Tickets', [('${C}', str(self.count))]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class PurpleTickets(Item):
|
||||
"""Some amount of purple tickets."""
|
||||
|
||||
count: Annotated[int, IOAttrs('c')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> ItemTypeID:
|
||||
return ItemTypeID.TICKETS_PURPLE
|
||||
|
||||
@override
|
||||
def get_description(self) -> tuple[str, list[tuple[str, str]]]:
|
||||
return '${C} Purple Tickets', [('${C}', str(self.count))]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class Tokens(Item):
|
||||
"""Some amount of tokens."""
|
||||
|
||||
count: Annotated[int, IOAttrs('c')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> ItemTypeID:
|
||||
return ItemTypeID.TOKENS
|
||||
|
||||
@override
|
||||
def get_description(self) -> tuple[str, list[tuple[str, str]]]:
|
||||
return '${C} Tokens', [('${C}', str(self.count))]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class Test(Item):
|
||||
"""Fills usable space for a display-item - good for calibration."""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> ItemTypeID:
|
||||
return ItemTypeID.TEST
|
||||
|
||||
@override
|
||||
def get_description(self) -> tuple[str, list[tuple[str, str]]]:
|
||||
return 'Test', []
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class Wrapper:
|
||||
"""Wraps a display-item and some baked out info.
|
||||
|
||||
This allows clients to at least give descriptions of new
|
||||
display-item types they may not have locally.
|
||||
"""
|
||||
|
||||
item: Annotated[Item, IOAttrs('i')]
|
||||
description: Annotated[str, IOAttrs('d')]
|
||||
description_subs: Annotated[list[str] | None, IOAttrs('s')]
|
||||
|
||||
@classmethod
|
||||
def for_item(cls, item: Item) -> Wrapper:
|
||||
"""Convenience method to wrap a display-item."""
|
||||
desc, subs = item.get_description()
|
||||
return Wrapper(item, desc, pairs_to_flat(subs))
|
||||
30
dist/ba_data/python/bacommon/docui/__init__.py
vendored
Normal file
30
dist/ba_data/python/bacommon/docui/__init__.py
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Declarative UI system.
|
||||
|
||||
A high level way to build UIs that lives as a layer on top of engine
|
||||
apis such as :mod:`bauiv1`. UIs can easily be serialized to json data
|
||||
and be provided by webservers or other local or remote sources.
|
||||
"""
|
||||
|
||||
from bacommon.docui._docui import (
|
||||
DocUIRequest,
|
||||
DocUIRequestTypeID,
|
||||
UnknownDocUIRequest,
|
||||
DocUIResponse,
|
||||
DocUIResponseTypeID,
|
||||
UnknownDocUIResponse,
|
||||
DocUIWebRequest,
|
||||
DocUIWebResponse,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
'DocUIRequest',
|
||||
'DocUIRequestTypeID',
|
||||
'UnknownDocUIRequest',
|
||||
'DocUIResponse',
|
||||
'DocUIResponseTypeID',
|
||||
'UnknownDocUIResponse',
|
||||
'DocUIWebRequest',
|
||||
'DocUIWebResponse',
|
||||
]
|
||||
172
dist/ba_data/python/bacommon/docui/_docui.py
vendored
Normal file
172
dist/ba_data/python/bacommon/docui/_docui.py
vendored
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Version 1 of our doc-ui system."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass
|
||||
from typing import override, assert_never, TYPE_CHECKING, Annotated
|
||||
|
||||
from efro.dataclassio import ioprepped, IOAttrs, IOMultiType
|
||||
from bacommon.locale import Locale
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
|
||||
class DocUIRequestTypeID(Enum):
|
||||
"""Type ID for each of our subclasses."""
|
||||
|
||||
UNKNOWN = 'u'
|
||||
V1 = 'v1'
|
||||
|
||||
|
||||
class DocUIRequest(IOMultiType[DocUIRequestTypeID]):
|
||||
"""A request for some UI."""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> DocUIRequestTypeID:
|
||||
# Require child classes to supply this themselves. If we did a
|
||||
# full type registry/lookup here it would require us to import
|
||||
# everything and would prevent lazy loading.
|
||||
raise NotImplementedError()
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type(cls, type_id: DocUIRequestTypeID) -> type[DocUIRequest]:
|
||||
"""Return the subclass for each of our type-ids."""
|
||||
# pylint: disable=cyclic-import
|
||||
|
||||
t = DocUIRequestTypeID
|
||||
if type_id is t.UNKNOWN:
|
||||
return UnknownDocUIRequest
|
||||
if type_id is t.V1:
|
||||
from bacommon.docui.v1 import Request
|
||||
|
||||
return Request
|
||||
|
||||
# Make sure we cover all types.
|
||||
assert_never(type_id)
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_unknown_type_fallback(cls) -> DocUIRequest:
|
||||
# If we encounter some future type we don't know anything about,
|
||||
# drop in a placeholder.
|
||||
return UnknownDocUIRequest()
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id_storage_name(cls) -> str:
|
||||
return '_t'
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class UnknownDocUIRequest(DocUIRequest):
|
||||
"""Fallback type for unrecognized UI types.
|
||||
|
||||
Will show the client a 'cannot display this UI' placeholder request.
|
||||
"""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> DocUIRequestTypeID:
|
||||
return DocUIRequestTypeID.UNKNOWN
|
||||
|
||||
|
||||
class DocUIResponseTypeID(Enum):
|
||||
"""Type ID for each of our subclasses."""
|
||||
|
||||
UNKNOWN = 'u'
|
||||
V1 = 'v1'
|
||||
|
||||
|
||||
class DocUIResponse(IOMultiType[DocUIResponseTypeID]):
|
||||
"""A UI provied in response to a :class:`DocUIRequest`."""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> DocUIResponseTypeID:
|
||||
# Require child classes to supply this themselves. If we did a
|
||||
# full type registry/lookup here it would require us to import
|
||||
# everything and would prevent lazy loading.
|
||||
raise NotImplementedError()
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type(cls, type_id: DocUIResponseTypeID) -> type[DocUIResponse]:
|
||||
"""Return the subclass for each of our type-ids."""
|
||||
# pylint: disable=cyclic-import
|
||||
|
||||
t = DocUIResponseTypeID
|
||||
if type_id is t.UNKNOWN:
|
||||
return UnknownDocUIResponse
|
||||
if type_id is t.V1:
|
||||
from bacommon.docui.v1 import Response
|
||||
|
||||
return Response
|
||||
|
||||
# Make sure we cover all types.
|
||||
assert_never(type_id)
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_unknown_type_fallback(cls) -> DocUIResponse:
|
||||
# If we encounter some future type we don't know anything about,
|
||||
# drop in a placeholder.
|
||||
return UnknownDocUIResponse()
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id_storage_name(cls) -> str:
|
||||
return '_t'
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class UnknownDocUIResponse(DocUIResponse):
|
||||
"""Fallback type for unrecognized UI types.
|
||||
|
||||
Will show the client a 'cannot display this UI' placeholder response.
|
||||
"""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> DocUIResponseTypeID:
|
||||
return DocUIResponseTypeID.UNKNOWN
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class DocUIWebRequest:
|
||||
"""Complete data sent for doc-ui http requests."""
|
||||
|
||||
#: The wrapped doc-ui request.
|
||||
doc_ui_request: Annotated[DocUIRequest, IOAttrs('r')]
|
||||
|
||||
#: The current locale of the client. doc-ui generally deals in raw
|
||||
#: strings and expects localization to happen on the server.
|
||||
locale: Annotated[Locale, IOAttrs('l')]
|
||||
|
||||
#: Engine build number. In some cases it may make sense to adjust
|
||||
#: responses depending on available engine features.
|
||||
engine_build_number: Annotated[int, IOAttrs('b')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class DocUIWebResponse:
|
||||
"""Complete data returned for doc-ui http requests."""
|
||||
|
||||
#: Human readable error string (if an error occurs). Either this or
|
||||
#: doc_ui_response should be set; not both.
|
||||
error: Annotated[str | None, IOAttrs('e', store_default=False)] = None
|
||||
|
||||
#: doc-ui response. Either this or error should be set; not both.
|
||||
doc_ui_response: Annotated[
|
||||
DocUIResponse | None,
|
||||
IOAttrs('r', store_default=False),
|
||||
] = None
|
||||
783
dist/ba_data/python/bacommon/docui/v1.py
vendored
Normal file
783
dist/ba_data/python/bacommon/docui/v1.py
vendored
Normal file
|
|
@ -0,0 +1,783 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Version 1 doc-ui types."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Annotated, override, assert_never
|
||||
|
||||
from efro.dataclassio import ioprepped, IOAttrs, IOMultiType
|
||||
|
||||
import bacommon.displayitem as ditm
|
||||
import bacommon.clienteffect as clfx
|
||||
from bacommon.docui._docui import (
|
||||
DocUIRequest,
|
||||
DocUIRequestTypeID,
|
||||
DocUIResponse,
|
||||
DocUIResponseTypeID,
|
||||
)
|
||||
|
||||
|
||||
class RequestMethod(Enum):
|
||||
"""Typeof of requests that can be made to doc-ui servers."""
|
||||
|
||||
#: An unknown request method. This can appear if a newer client is
|
||||
#: requesting some method from an older server that is not known to
|
||||
#: the server.
|
||||
UNKNOWN = 'u'
|
||||
|
||||
#: Fetch some resource. This can be retried and its results can
|
||||
#: optionally be cached for some amount of time.
|
||||
GET = 'g'
|
||||
|
||||
#: Change some resource. This cannot be implicitly retried (at least
|
||||
#: without deduplication), nor can it be cached.
|
||||
POST = 'p'
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class Request(DocUIRequest):
|
||||
"""Full request to doc-ui."""
|
||||
|
||||
path: Annotated[str, IOAttrs('p')]
|
||||
method: Annotated[
|
||||
RequestMethod,
|
||||
IOAttrs('m', store_default=False, enum_fallback=RequestMethod.UNKNOWN),
|
||||
] = RequestMethod.GET
|
||||
args: Annotated[dict, IOAttrs('r', store_default=False)] = field(
|
||||
default_factory=dict
|
||||
)
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> DocUIRequestTypeID:
|
||||
return DocUIRequestTypeID.V1
|
||||
|
||||
|
||||
class ActionTypeID(Enum):
|
||||
"""Type ID for each of our subclasses."""
|
||||
|
||||
BROWSE = 'b'
|
||||
REPLACE = 'r'
|
||||
LOCAL = 'l'
|
||||
UNKNOWN = 'u'
|
||||
|
||||
|
||||
class Action(IOMultiType[ActionTypeID]):
|
||||
"""Top level class for our multitype."""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> ActionTypeID:
|
||||
# Require child classes to supply this themselves. If we did a
|
||||
# full type registry/lookup here it would require us to import
|
||||
# everything and would prevent lazy loading.
|
||||
raise NotImplementedError()
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type(cls, type_id: ActionTypeID) -> type[Action]:
|
||||
"""Return the subclass for each of our type-ids."""
|
||||
# pylint: disable=cyclic-import
|
||||
|
||||
t = ActionTypeID
|
||||
if type_id is t.BROWSE:
|
||||
return Browse
|
||||
if type_id is t.REPLACE:
|
||||
return Replace
|
||||
if type_id is t.LOCAL:
|
||||
return Local
|
||||
if type_id is t.UNKNOWN:
|
||||
return UnknownAction
|
||||
|
||||
# Important to make sure we provide all types.
|
||||
assert_never(type_id)
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id_storage_name(cls) -> str:
|
||||
return '_t'
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_unknown_type_fallback(cls) -> Action:
|
||||
# If we encounter some future type we don't know anything about,
|
||||
# drop in a placeholder.
|
||||
return UnknownAction()
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class UnknownAction(Action):
|
||||
"""Action type we don't recognize."""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> ActionTypeID:
|
||||
return ActionTypeID.UNKNOWN
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class Browse(Action):
|
||||
"""Browse to a new page in a new window."""
|
||||
|
||||
request: Annotated[Request, IOAttrs('r')]
|
||||
|
||||
#: Plays a swish.
|
||||
default_sound: Annotated[bool, IOAttrs('ds', store_default=False)] = True
|
||||
|
||||
#: Client-effects to run immediately when the button is pressed.
|
||||
#:
|
||||
#: :meta private:
|
||||
immediate_client_effects: Annotated[
|
||||
list[clfx.Effect], IOAttrs('fx', store_default=False)
|
||||
] = field(default_factory=list)
|
||||
|
||||
#: Local action to run immediately when the button is pressed. Will
|
||||
#: be handled by
|
||||
#: :meth:`bauiv1lib.docui.DocUIController.local_action()`.
|
||||
immediate_local_action: Annotated[
|
||||
str | None, IOAttrs('a', store_default=False)
|
||||
] = None
|
||||
immediate_local_action_args: Annotated[
|
||||
dict | None, IOAttrs('aa', store_default=False)
|
||||
] = None
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> ActionTypeID:
|
||||
return ActionTypeID.BROWSE
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class Replace(Action):
|
||||
"""Replace current page with a new one.
|
||||
|
||||
Should be used to effectively 'modify' existing UIs by replacing
|
||||
them with something slightly different. Things like scroll position
|
||||
and selection will be carried across to the new layout when possible
|
||||
to make for a seamless transition.
|
||||
"""
|
||||
|
||||
request: Annotated[Request, IOAttrs('r')]
|
||||
|
||||
#: Plays a click if triggered by a button press.
|
||||
default_sound: Annotated[bool, IOAttrs('ds', store_default=False)] = True
|
||||
|
||||
#: Client-effects to run immediately when the button is pressed.
|
||||
#:
|
||||
#: :meta private:
|
||||
immediate_client_effects: Annotated[
|
||||
list[clfx.Effect], IOAttrs('fx', store_default=False)
|
||||
] = field(default_factory=list)
|
||||
|
||||
#: Local action to run immediately when the button is pressed. Will
|
||||
#: be handled by
|
||||
#: :meth:`bauiv1lib.docui.DocUIController.local_action()`.
|
||||
immediate_local_action: Annotated[
|
||||
str | None, IOAttrs('a', store_default=False)
|
||||
] = None
|
||||
immediate_local_action_args: Annotated[
|
||||
dict | None, IOAttrs('aa', store_default=False)
|
||||
] = None
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> ActionTypeID:
|
||||
return ActionTypeID.REPLACE
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class Local(Action):
|
||||
"""Perform only local actions; no new requests or page changes."""
|
||||
|
||||
close_window: Annotated[bool, IOAttrs('c', store_default=False)] = False
|
||||
|
||||
#: Plays a swish if closing the window or a click if triggered by a
|
||||
#: button press.
|
||||
default_sound: Annotated[bool, IOAttrs('ds', store_default=False)] = True
|
||||
|
||||
#: Client-effects to run immediately when the button is pressed.
|
||||
#:
|
||||
#: :meta private:
|
||||
immediate_client_effects: Annotated[
|
||||
list[clfx.Effect], IOAttrs('fx', store_default=False)
|
||||
] = field(default_factory=list)
|
||||
|
||||
#: Local action to run immediately when the button is pressed. Will
|
||||
#: be handled by
|
||||
#: :meth:`bauiv1lib.docui.DocUIController.local_action()`.
|
||||
immediate_local_action: Annotated[
|
||||
str | None, IOAttrs('a', store_default=False)
|
||||
] = None
|
||||
immediate_local_action_args: Annotated[
|
||||
dict | None, IOAttrs('aa', store_default=False)
|
||||
] = None
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> ActionTypeID:
|
||||
return ActionTypeID.LOCAL
|
||||
|
||||
|
||||
class HAlign(Enum):
|
||||
"""Horizontal alignment."""
|
||||
|
||||
LEFT = 'l'
|
||||
CENTER = 'c'
|
||||
RIGHT = 'r'
|
||||
|
||||
|
||||
class VAlign(Enum):
|
||||
"""Vertical alignment."""
|
||||
|
||||
TOP = 't'
|
||||
CENTER = 'c'
|
||||
BOTTOM = 'b'
|
||||
|
||||
|
||||
class DecorationTypeID(Enum):
|
||||
"""Type ID for each of our subclasses."""
|
||||
|
||||
UNKNOWN = 'u'
|
||||
TEXT = 't'
|
||||
IMAGE = 'i'
|
||||
DISPLAY_ITEM = 'd'
|
||||
|
||||
|
||||
class Decoration(IOMultiType[DecorationTypeID]):
|
||||
"""Top level class for our multitype."""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> DecorationTypeID:
|
||||
# Require child classes to supply this themselves. If we did a
|
||||
# full type registry/lookup here it would require us to import
|
||||
# everything and would prevent lazy loading.
|
||||
raise NotImplementedError()
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type(cls, type_id: DecorationTypeID) -> type[Decoration]:
|
||||
# pylint: disable=cyclic-import
|
||||
|
||||
t = DecorationTypeID
|
||||
if type_id is t.UNKNOWN:
|
||||
return UnknownDecoration
|
||||
if type_id is t.TEXT:
|
||||
return Text
|
||||
if type_id is t.IMAGE:
|
||||
return Image
|
||||
if type_id is t.DISPLAY_ITEM:
|
||||
return DisplayItem
|
||||
|
||||
# Important to make sure we provide all types.
|
||||
assert_never(type_id)
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_unknown_type_fallback(cls) -> Decoration:
|
||||
# If we encounter some future type we don't know anything about,
|
||||
# drop in a placeholder.
|
||||
return UnknownDecoration()
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id_storage_name(cls) -> str:
|
||||
return '_t'
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class UnknownDecoration(Decoration):
|
||||
"""An unknown decoration.
|
||||
|
||||
In practice these should never show up since the master-server
|
||||
generates these on the fly for the client and so should not send
|
||||
clients one they can't digest.
|
||||
"""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> DecorationTypeID:
|
||||
return DecorationTypeID.UNKNOWN
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class Text(Decoration):
|
||||
"""Text decoration."""
|
||||
|
||||
#: Note that doc-ui accepts only raw :class:`str` values for text;
|
||||
#: use :meth:`babase.Lstr.evaluate()` or whatnot for multi-language
|
||||
#: support.
|
||||
text: Annotated[str, IOAttrs('t')]
|
||||
position: Annotated[tuple[float, float], IOAttrs('p')]
|
||||
|
||||
#: Note that this effectively is max-width and max-height.
|
||||
size: Annotated[tuple[float, float], IOAttrs('i')]
|
||||
scale: Annotated[float, IOAttrs('s', store_default=False)] = 1.0
|
||||
h_align: Annotated[HAlign, IOAttrs('ha', store_default=False)] = (
|
||||
HAlign.CENTER
|
||||
)
|
||||
v_align: Annotated[VAlign, IOAttrs('va', store_default=False)] = (
|
||||
VAlign.CENTER
|
||||
)
|
||||
color: Annotated[
|
||||
tuple[float, float, float, float] | None,
|
||||
IOAttrs('c', store_default=False),
|
||||
] = None
|
||||
flatness: Annotated[float | None, IOAttrs('f', store_default=False)] = None
|
||||
shadow: Annotated[float | None, IOAttrs('sh', store_default=False)] = None
|
||||
|
||||
is_lstr: Annotated[bool, IOAttrs('l', store_default=False)] = False
|
||||
|
||||
highlight: Annotated[bool, IOAttrs('h', store_default=False)] = True
|
||||
depth_range: Annotated[tuple[float, float] | None, IOAttrs('z')] = None
|
||||
|
||||
#: Show max-width/height bounds; useful during development.
|
||||
debug: Annotated[bool, IOAttrs('d', store_default=False)] = False
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> DecorationTypeID:
|
||||
return DecorationTypeID.TEXT
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class Image(Decoration):
|
||||
"""Image decoration."""
|
||||
|
||||
texture: Annotated[str, IOAttrs('t')]
|
||||
position: Annotated[tuple[float, float], IOAttrs('p')]
|
||||
size: Annotated[tuple[float, float], IOAttrs('s')]
|
||||
color: Annotated[
|
||||
tuple[float, float, float, float] | None,
|
||||
IOAttrs('c', store_default=False),
|
||||
] = None
|
||||
h_align: Annotated[HAlign, IOAttrs('ha', store_default=False)] = (
|
||||
HAlign.CENTER
|
||||
)
|
||||
v_align: Annotated[VAlign, IOAttrs('va', store_default=False)] = (
|
||||
VAlign.CENTER
|
||||
)
|
||||
tint_texture: Annotated[str | None, IOAttrs('tt', store_default=False)] = (
|
||||
None
|
||||
)
|
||||
tint_color: Annotated[
|
||||
tuple[float, float, float] | None, IOAttrs('tc1', store_default=False)
|
||||
] = None
|
||||
tint2_color: Annotated[
|
||||
tuple[float, float, float] | None, IOAttrs('tc2', store_default=False)
|
||||
] = None
|
||||
mask_texture: Annotated[str | None, IOAttrs('mt', store_default=False)] = (
|
||||
None
|
||||
)
|
||||
mesh_opaque: Annotated[str | None, IOAttrs('mo', store_default=False)] = (
|
||||
None
|
||||
)
|
||||
mesh_transparent: Annotated[
|
||||
str | None, IOAttrs('mn', store_default=False)
|
||||
] = None
|
||||
highlight: Annotated[bool, IOAttrs('h', store_default=False)] = True
|
||||
depth_range: Annotated[tuple[float, float] | None, IOAttrs('z')] = None
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> DecorationTypeID:
|
||||
return DecorationTypeID.IMAGE
|
||||
|
||||
|
||||
class DisplayItemStyle(Enum):
|
||||
"""Styles a display-item can be drawn in."""
|
||||
|
||||
#: Shows graphics and/or text fully conveying what the item is. Fits
|
||||
#: in to a 4:3 box and works best with large-ish displays.
|
||||
FULL = 'f'
|
||||
|
||||
#: Graphics and/or text fully conveying what the item is, but
|
||||
#: condensed to fit in a 2:1 box displayed at small sizes.
|
||||
COMPACT = 'c'
|
||||
|
||||
#: A graphics-only representation of the item (though text may be
|
||||
#: used in fallback cases). Does not fully convey what the item is,
|
||||
#: but instead is intended to be used alongside the item's textual
|
||||
#: description. For example, some number of coins may simply display
|
||||
#: a coin graphic here without the number. Draws in a 1:1 box and
|
||||
#: works for large or small display.
|
||||
ICON = 'i'
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class DisplayItem(Decoration):
|
||||
"""DisplayItem decoration."""
|
||||
|
||||
wrapper: Annotated[ditm.Wrapper, IOAttrs('w')]
|
||||
position: Annotated[tuple[float, float], IOAttrs('p')]
|
||||
size: Annotated[tuple[float, float], IOAttrs('s')]
|
||||
style: Annotated[DisplayItemStyle, IOAttrs('t', store_default=False)] = (
|
||||
DisplayItemStyle.FULL
|
||||
)
|
||||
text_color: Annotated[
|
||||
tuple[float, float, float] | None, IOAttrs('c', store_default=False)
|
||||
] = None
|
||||
highlight: Annotated[bool, IOAttrs('h', store_default=False)] = True
|
||||
depth_range: Annotated[tuple[float, float] | None, IOAttrs('z')] = None
|
||||
debug: Annotated[bool, IOAttrs('d', store_default=False)] = False
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> DecorationTypeID:
|
||||
return DecorationTypeID.DISPLAY_ITEM
|
||||
|
||||
|
||||
class ButtonStyle(Enum):
|
||||
"""Styles a button can be."""
|
||||
|
||||
SQUARE = 'q'
|
||||
TAB = 't'
|
||||
SMALL = 's'
|
||||
MEDIUM = 'm'
|
||||
LARGE = 'l'
|
||||
LARGER = 'xl'
|
||||
BACK = 'b'
|
||||
BACK_SMALL = 'bs'
|
||||
SQUARE_WIDE = 'w'
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class Button:
|
||||
"""A button in our doc-ui.
|
||||
|
||||
Note that size, padding, and all decorations are scaled consistently
|
||||
with 'scale'.
|
||||
"""
|
||||
|
||||
#: Note that doc-ui accepts only raw :class:`str` values for text;
|
||||
#: use :meth:`babase.Lstr.evaluate()` or whatnot for multi-language
|
||||
#: support.
|
||||
label: Annotated[str | None, IOAttrs('l', store_default=False)] = None
|
||||
|
||||
action: Annotated[Action | None, IOAttrs('a', store_default=False)] = None
|
||||
|
||||
size: Annotated[
|
||||
tuple[float, float] | None, IOAttrs('sz', store_default=False)
|
||||
] = None
|
||||
color: Annotated[
|
||||
tuple[float, float, float, float] | None,
|
||||
IOAttrs('cl', store_default=False),
|
||||
] = None
|
||||
label_color: Annotated[
|
||||
tuple[float, float, float, float] | None,
|
||||
IOAttrs('lc', store_default=False),
|
||||
] = None
|
||||
label_flatness: Annotated[
|
||||
float | None, IOAttrs('lf', store_default=False)
|
||||
] = None
|
||||
label_scale: Annotated[float | None, IOAttrs('ls', store_default=False)] = (
|
||||
None
|
||||
)
|
||||
label_is_lstr: Annotated[bool, IOAttrs('ll', store_default=False)] = False
|
||||
texture: Annotated[str | None, IOAttrs('tex', store_default=False)] = None
|
||||
scale: Annotated[float, IOAttrs('sc', store_default=False)] = 1.0
|
||||
padding_left: Annotated[float, IOAttrs('pl', store_default=False)] = 0.0
|
||||
padding_top: Annotated[float, IOAttrs('pt', store_default=False)] = 0.0
|
||||
padding_right: Annotated[float, IOAttrs('pr', store_default=False)] = 0.0
|
||||
padding_bottom: Annotated[float, IOAttrs('pb', store_default=False)] = 0.0
|
||||
decorations: Annotated[
|
||||
list[Decoration] | None, IOAttrs('c', store_default=False)
|
||||
] = None
|
||||
style: Annotated[ButtonStyle, IOAttrs('y', store_default=False)] = (
|
||||
ButtonStyle.SQUARE
|
||||
)
|
||||
default: Annotated[bool, IOAttrs('df', store_default=False)] = False
|
||||
selected: Annotated[bool, IOAttrs('sel', store_default=False)] = False
|
||||
|
||||
icon: Annotated[str | None, IOAttrs('icn', store_default=False)] = None
|
||||
icon_scale: Annotated[float | None, IOAttrs('is', store_default=False)] = (
|
||||
None
|
||||
)
|
||||
icon_color: Annotated[
|
||||
tuple[float, float, float, float] | None,
|
||||
IOAttrs('ic', store_default=False),
|
||||
] = None
|
||||
depth_range: Annotated[
|
||||
tuple[float, float] | None, IOAttrs('z', store_default=None)
|
||||
] = None
|
||||
|
||||
#: Custom widget id. Will be prefixed with window id, but must be
|
||||
#: unique within the window.
|
||||
widget_id: Annotated[str | None, IOAttrs('i', store_default=False)] = None
|
||||
|
||||
#: Draw bounds of the button.
|
||||
debug: Annotated[bool, IOAttrs('d', store_default=False)] = False
|
||||
|
||||
|
||||
class RowTypeID(Enum):
|
||||
"""Type ID for each of our subclasses."""
|
||||
|
||||
BUTTON_ROW = 'b'
|
||||
UNKNOWN = 'u'
|
||||
|
||||
|
||||
class Row(IOMultiType[RowTypeID]):
|
||||
"""Top level class for our multitype."""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> RowTypeID:
|
||||
# Require child classes to supply this themselves. If we did a
|
||||
# full type registry/lookup here it would require us to import
|
||||
# everything and would prevent lazy loading.
|
||||
raise NotImplementedError()
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type(cls, type_id: RowTypeID) -> type[Row]:
|
||||
"""Return the subclass for each of our type-ids."""
|
||||
# pylint: disable=cyclic-import
|
||||
|
||||
t = RowTypeID
|
||||
if type_id is t.UNKNOWN:
|
||||
return UnknownRow
|
||||
if type_id is t.BUTTON_ROW:
|
||||
return ButtonRow
|
||||
|
||||
# Important to make sure we provide all types.
|
||||
assert_never(type_id)
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_unknown_type_fallback(cls) -> Row:
|
||||
# If we encounter some future type we don't know anything about,
|
||||
# drop in a placeholder.
|
||||
return UnknownRow()
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id_storage_name(cls) -> str:
|
||||
return '_t'
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class UnknownRow(Row):
|
||||
"""A row type we don't have."""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> RowTypeID:
|
||||
return RowTypeID.UNKNOWN
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ButtonRow(Row):
|
||||
"""A row consisting of buttons."""
|
||||
|
||||
buttons: Annotated[list[Button], IOAttrs('b')]
|
||||
|
||||
header_height: Annotated[float, IOAttrs('h', store_default=False)] = 0.0
|
||||
header_scale: Annotated[float, IOAttrs('hs', store_default=False)] = 1.0
|
||||
header_decorations_left: Annotated[
|
||||
list[Decoration] | None, IOAttrs('hdl', store_default=False)
|
||||
] = None
|
||||
header_decorations_center: Annotated[
|
||||
list[Decoration] | None, IOAttrs('hdc', store_default=False)
|
||||
] = None
|
||||
header_decorations_right: Annotated[
|
||||
list[Decoration] | None, IOAttrs('hdr', store_default=False)
|
||||
] = None
|
||||
|
||||
#: Note that doc-ui accepts only raw :class:`str` values for text;
|
||||
#: use :meth:`babase.Lstr.evaluate()` or whatnot for multi-language
|
||||
#: support.
|
||||
title: Annotated[str | None, IOAttrs('t', store_default=False)] = None
|
||||
title_color: Annotated[
|
||||
tuple[float, float, float, float] | None,
|
||||
IOAttrs('tc', store_default=False),
|
||||
] = None
|
||||
title_flatness: Annotated[
|
||||
float | None, IOAttrs('tf', store_default=False)
|
||||
] = None
|
||||
title_shadow: Annotated[
|
||||
float | None, IOAttrs('ts', store_default=False)
|
||||
] = None
|
||||
title_is_lstr: Annotated[bool, IOAttrs('tl', store_default=False)] = False
|
||||
subtitle: Annotated[str | None, IOAttrs('s', store_default=False)] = None
|
||||
subtitle_color: Annotated[
|
||||
tuple[float, float, float, float] | None,
|
||||
IOAttrs('sc', store_default=False),
|
||||
] = None
|
||||
subtitle_flatness: Annotated[
|
||||
float | None, IOAttrs('sf', store_default=False)
|
||||
] = None
|
||||
subtitle_shadow: Annotated[
|
||||
float | None, IOAttrs('ss', store_default=False)
|
||||
] = None
|
||||
subtitle_is_lstr: Annotated[bool, IOAttrs('sl', store_default=False)] = (
|
||||
False
|
||||
)
|
||||
|
||||
#: Spacing between all buttons in the row.
|
||||
button_spacing: Annotated[float, IOAttrs('bs', store_default=False)] = 15.0
|
||||
|
||||
#: Padding on the left of the row's horizonally-scrollable area.
|
||||
padding_left: Annotated[float, IOAttrs('pl', store_default=False)] = 10.0
|
||||
#: Padding on the right of the row's horizonally-scrollable area.
|
||||
padding_right: Annotated[float, IOAttrs('pr', store_default=False)] = 10.0
|
||||
#: Padding on the top of the row's horizonally-scrollable area.
|
||||
padding_top: Annotated[float, IOAttrs('pt', store_default=False)] = 10.0
|
||||
#: Padding on the bottom of the row's horizonally-scrollable area.
|
||||
padding_bottom: Annotated[float, IOAttrs('pb', store_default=False)] = 10.0
|
||||
|
||||
#: Extra space above the row's horizontally-scrollable area.
|
||||
spacing_top: Annotated[float, IOAttrs('st', store_default=False)] = 0.0
|
||||
|
||||
#: Extra space below the row's horizontally-scrollable area.
|
||||
spacing_bottom: Annotated[float, IOAttrs('sb', store_default=False)] = 0.0
|
||||
|
||||
center_content: Annotated[bool, IOAttrs('c', store_default=False)] = False
|
||||
center_title: Annotated[bool, IOAttrs('ct', store_default=False)] = False
|
||||
|
||||
#: If things disappear when scrolling left/right, turn this up.
|
||||
simple_culling_h: Annotated[float, IOAttrs('sch', store_default=False)] = (
|
||||
100.0
|
||||
)
|
||||
|
||||
#: Draw bounds of the overall row and individual button columns
|
||||
#: (including padding). The UI will scroll to keep these areas
|
||||
#: visible in their entirety when changing selection via directional
|
||||
#: controls, so try to make sure all decorations for a button are
|
||||
#: within these bounds.
|
||||
debug: Annotated[bool, IOAttrs('d', store_default=False)] = False
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> RowTypeID:
|
||||
return RowTypeID.BUTTON_ROW
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class Page:
|
||||
"""Doc-UI page version 1."""
|
||||
|
||||
#: Note that doc-ui accepts only raw :class:`str` values for text;
|
||||
#: use :meth:`babase.Lstr.evaluate()` or whatnot for multi-language
|
||||
#: support.
|
||||
title: Annotated[str, IOAttrs('t')]
|
||||
rows: Annotated[list[Row], IOAttrs('r')]
|
||||
|
||||
#: If True, content smaller than the available height will be
|
||||
#: centered vertically. This can look natural for certain types of
|
||||
#: content such as confirmation dialogs.
|
||||
center_vertically: Annotated[bool, IOAttrs('cv', store_default=False)] = (
|
||||
False
|
||||
)
|
||||
|
||||
row_spacing: Annotated[float, IOAttrs('s', store_default=False)] = 10.0
|
||||
|
||||
#: If things disappear when scrolling up and down, turn this up.
|
||||
simple_culling_v: Annotated[float, IOAttrs('scv', store_default=False)] = (
|
||||
100.0
|
||||
)
|
||||
|
||||
#: Whether the title is a json dict representing an Lstr. Generally
|
||||
#: doc-ui translation should be handled server-side, but this can
|
||||
#: allow client-side translation.
|
||||
title_is_lstr: Annotated[bool, IOAttrs('tl', store_default=False)] = False
|
||||
|
||||
padding_bottom: Annotated[float, IOAttrs('pb', store_default=False)] = 0.0
|
||||
padding_left: Annotated[float, IOAttrs('pl', store_default=False)] = 0.0
|
||||
padding_top: Annotated[float, IOAttrs('pt', store_default=False)] = 0.0
|
||||
padding_right: Annotated[float, IOAttrs('pr', store_default=False)] = 0.0
|
||||
|
||||
|
||||
class ResponseStatus(Enum):
|
||||
"""The overall result of a request."""
|
||||
|
||||
SUCCESS = 0
|
||||
|
||||
#: Something went wrong. That's all we know.
|
||||
UNKNOWN_ERROR = 1
|
||||
|
||||
#: Something went wrong talking to the server. A 'Retry' button may
|
||||
#: be appropriate to show here (for GET requests at least).
|
||||
COMMUNICATION_ERROR = 2
|
||||
|
||||
#: This requires the user to be signed in, and they aint.
|
||||
NOT_SIGNED_IN_ERROR = 3
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class Response(DocUIResponse):
|
||||
"""Full docui response."""
|
||||
|
||||
page: Annotated[Page, IOAttrs('p')]
|
||||
status: Annotated[ResponseStatus, IOAttrs('s', store_default=False)] = (
|
||||
ResponseStatus.SUCCESS
|
||||
)
|
||||
|
||||
#: Effects to run on the client when this response is initially
|
||||
#: received. Note that these effects will not re-run if the page is
|
||||
#: automatically refreshed later (due to window resizing, back
|
||||
#: navigation, etc).
|
||||
#:
|
||||
#: :meta private:
|
||||
client_effects: Annotated[
|
||||
list[clfx.Effect], IOAttrs('fx', store_default=False)
|
||||
] = field(default_factory=list)
|
||||
|
||||
#: Local action to run after this response is initially received.
|
||||
#: Will be handled by
|
||||
#: :meth:`bauiv1lib.docui.DocUIController.local_action()`. Note that
|
||||
#: these actions will not re-run if the page is automatically
|
||||
#: refreshed later (due to window resizing, back navigation, etc).
|
||||
local_action: Annotated[str | None, IOAttrs('a', store_default=False)] = (
|
||||
None
|
||||
)
|
||||
local_action_args: Annotated[
|
||||
dict | None, IOAttrs('aa', store_default=False)
|
||||
] = None
|
||||
|
||||
#: New overall action to have the client schedule after this
|
||||
#: response is received. Useful for redirecting to other pages or
|
||||
#: closing the doc-ui window.
|
||||
timed_action: Annotated[
|
||||
Action | None, IOAttrs('ta', store_default=False)
|
||||
] = None
|
||||
timed_action_delay: Annotated[
|
||||
float, IOAttrs('tad', store_default=False)
|
||||
] = 0.0
|
||||
|
||||
#: If provided, error on builds older than this (can be used to gate
|
||||
#: functionality without bumping entire docui version).
|
||||
minimum_engine_build: Annotated[
|
||||
int | None, IOAttrs('b', store_default=False)
|
||||
] = None
|
||||
|
||||
#: The client maintains some persistent state (such as widget
|
||||
#: selection) for all pages viewed. The default index for these
|
||||
#: states is the path of the request. If a server returns a
|
||||
#: significant variety of responses for a single path, however,
|
||||
#: (based on args, etc) then it may make sense for the server to
|
||||
#: provide explicit state ids for those different variations.
|
||||
shared_state_id: Annotated[
|
||||
str | None, IOAttrs('t', store_default=False)
|
||||
] = None
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> DocUIResponseTypeID:
|
||||
return DocUIResponseTypeID.V1
|
||||
30
dist/ba_data/python/bacommon/locale.py
vendored
30
dist/ba_data/python/bacommon/locale.py
vendored
|
|
@ -71,6 +71,7 @@ class Locale(Enum):
|
|||
VENETIAN = 'venetn'
|
||||
VIETNAMESE = 'viet'
|
||||
KAZAKH = 'kazk'
|
||||
JAPANESE = 'jpn'
|
||||
|
||||
# Note: We use if-statement chains here so we can use assert_never()
|
||||
# to ensure we cover all existing values. But we cache lookups so
|
||||
|
|
@ -87,7 +88,7 @@ class Locale(Enum):
|
|||
# pylint: disable=too-many-branches
|
||||
# pylint: disable=too-many-return-statements
|
||||
|
||||
cls = type(self)
|
||||
cls = Locale
|
||||
|
||||
if self is cls.ENGLISH:
|
||||
return 'English'
|
||||
|
|
@ -175,6 +176,8 @@ class Locale(Enum):
|
|||
return 'Vietnamese'
|
||||
if self is cls.KAZAKH:
|
||||
return 'Kazakh'
|
||||
if self is cls.JAPANESE:
|
||||
return 'Japanese'
|
||||
|
||||
# Make sure we've covered all cases.
|
||||
assert_never(self)
|
||||
|
|
@ -206,7 +209,7 @@ class Locale(Enum):
|
|||
# pylint: disable=too-many-branches
|
||||
# pylint: disable=too-many-return-statements
|
||||
|
||||
cls = type(self)
|
||||
cls = Locale
|
||||
|
||||
if self is cls.ENGLISH:
|
||||
return 'English'
|
||||
|
|
@ -296,6 +299,8 @@ class Locale(Enum):
|
|||
return 'Vietnamese'
|
||||
if self is cls.KAZAKH:
|
||||
return 'Kazakh'
|
||||
if self is cls.JAPANESE:
|
||||
return 'Japanese'
|
||||
|
||||
# Make sure we've covered all cases.
|
||||
assert_never(self)
|
||||
|
|
@ -306,7 +311,7 @@ class Locale(Enum):
|
|||
# pylint: disable=too-many-return-statements
|
||||
# pylint: disable=too-many-branches
|
||||
|
||||
cls = type(self)
|
||||
cls = Locale
|
||||
R = LocaleResolved
|
||||
|
||||
if self is cls.ENGLISH:
|
||||
|
|
@ -389,6 +394,8 @@ class Locale(Enum):
|
|||
return R.VIETNAMESE
|
||||
if self is cls.KAZAKH:
|
||||
return R.KAZAKH
|
||||
if self is cls.JAPANESE:
|
||||
return R.JAPANESE
|
||||
|
||||
# Make sure we're covering all cases.
|
||||
assert_never(self)
|
||||
|
|
@ -444,6 +451,7 @@ class LocaleResolved(Enum):
|
|||
VENETIAN = 'venetn'
|
||||
VIETNAMESE = 'viet'
|
||||
KAZAKH = 'kazk'
|
||||
JAPANESE = 'jpn'
|
||||
|
||||
# Note: We use if-statement chains here so we can use assert_never()
|
||||
# to ensure we cover all existing values. But we cache lookups so
|
||||
|
|
@ -464,7 +472,7 @@ class LocaleResolved(Enum):
|
|||
# pylint: disable=too-many-return-statements
|
||||
# pylint: disable=too-many-branches
|
||||
|
||||
cls = type(self)
|
||||
cls = LocaleResolved
|
||||
|
||||
if self is cls.ENGLISH:
|
||||
return Locale.ENGLISH
|
||||
|
|
@ -546,6 +554,8 @@ class LocaleResolved(Enum):
|
|||
return Locale.VIETNAMESE
|
||||
if self is cls.KAZAKH:
|
||||
return Locale.KAZAKH
|
||||
if self is cls.JAPANESE:
|
||||
return Locale.JAPANESE
|
||||
|
||||
# Make sure we're covering all cases.
|
||||
assert_never(self)
|
||||
|
|
@ -561,7 +571,7 @@ class LocaleResolved(Enum):
|
|||
"""
|
||||
# pylint: disable=too-many-branches
|
||||
# pylint: disable=too-many-statements
|
||||
cls = type(self)
|
||||
cls = LocaleResolved
|
||||
|
||||
val: str | None = None
|
||||
|
||||
|
|
@ -647,6 +657,8 @@ class LocaleResolved(Enum):
|
|||
val = 'vi'
|
||||
elif self is cls.KAZAKH:
|
||||
val = 'kk'
|
||||
elif self is cls.JAPANESE:
|
||||
val = 'ja'
|
||||
else:
|
||||
# Make sure we cover all cases.
|
||||
assert_never(self)
|
||||
|
|
@ -668,9 +680,9 @@ class LocaleResolved(Enum):
|
|||
|
||||
return val
|
||||
|
||||
@classmethod
|
||||
@staticmethod
|
||||
@lru_cache(maxsize=128)
|
||||
def from_tag(cls, tag: str) -> LocaleResolved:
|
||||
def from_tag(tag: str) -> LocaleResolved:
|
||||
"""Return a locale for a given string tag.
|
||||
|
||||
Tags can be provided in BCP 47 form ('en-US') or POSIX locale
|
||||
|
|
@ -680,6 +692,8 @@ class LocaleResolved(Enum):
|
|||
# pylint: disable=too-many-statements
|
||||
# pylint: disable=too-many-return-statements
|
||||
|
||||
cls = LocaleResolved
|
||||
|
||||
# POSIX locale strings can contain a dot followed by an
|
||||
# encoding. Strip that off.
|
||||
tag2 = tag.split('.')[0]
|
||||
|
|
@ -838,6 +852,8 @@ class LocaleResolved(Enum):
|
|||
return cls.VIETNAMESE
|
||||
if lang == 'kk':
|
||||
return cls.KAZAKH
|
||||
if lang == 'ja':
|
||||
return cls.JAPANESE
|
||||
|
||||
# Make noise if we come across something unexpected so we can
|
||||
# add it.
|
||||
|
|
|
|||
|
|
@ -108,6 +108,11 @@ class LoggerControlConfig:
|
|||
for logname in existinglognames:
|
||||
logger = logging.getLogger(logname)
|
||||
if logger.getEffectiveLevel() != self.get_effective_level(logname):
|
||||
|
||||
# Exceptions for ones that I don't care to look into.
|
||||
if logname in {'pyasn1'}:
|
||||
continue
|
||||
|
||||
logging.error(
|
||||
'loggercontrol effective-level sanity check failed;'
|
||||
' expected logger %s to have effective level %s'
|
||||
|
|
|
|||
2
dist/ba_data/python/bacommon/logging.py
vendored
2
dist/ba_data/python/bacommon/logging.py
vendored
|
|
@ -48,7 +48,7 @@ class ClientLoggerName(Enum):
|
|||
"""Return a short description for the logger."""
|
||||
# pylint: disable=too-many-return-statements
|
||||
# pylint: disable=too-many-branches
|
||||
cls = type(self)
|
||||
cls = ClientLoggerName
|
||||
if self is cls.BA:
|
||||
return 'top level Ballistica logger - use to adjust everything'
|
||||
if self is cls.ENV:
|
||||
|
|
|
|||
4
dist/ba_data/python/bacommon/login.py
vendored
4
dist/ba_data/python/bacommon/login.py
vendored
|
|
@ -32,7 +32,7 @@ class LoginType(Enum):
|
|||
@property
|
||||
def displayname(self) -> str:
|
||||
"""A human readable name for this value."""
|
||||
cls = type(self)
|
||||
cls = LoginType
|
||||
match self:
|
||||
case cls.EMAIL:
|
||||
return 'Email/Password'
|
||||
|
|
@ -44,7 +44,7 @@ class LoginType(Enum):
|
|||
@property
|
||||
def displaynameshort(self) -> str:
|
||||
"""A short human readable name for this value."""
|
||||
cls = type(self)
|
||||
cls = LoginType
|
||||
match self:
|
||||
case cls.EMAIL:
|
||||
return 'Email'
|
||||
|
|
|
|||
8
dist/ba_data/python/bacommon/net.py
vendored
8
dist/ba_data/python/bacommon/net.py
vendored
|
|
@ -1,6 +1,12 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Network related data and functionality."""
|
||||
"""Network related data and functionality.
|
||||
|
||||
.. warning::
|
||||
|
||||
This is an internal api and subject to change at any time. Do not use
|
||||
it in mod code.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
|
|
|||
8
dist/ba_data/python/bacommon/securedata.py
vendored
8
dist/ba_data/python/bacommon/securedata.py
vendored
|
|
@ -1,6 +1,12 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to verifying ballistica server generated data."""
|
||||
"""Functionality related to verifying server generated data.
|
||||
|
||||
.. warning::
|
||||
|
||||
This is an internal api and subject to change at any time. Do not use
|
||||
it in mod code.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
from dataclasses import dataclass
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to the server manager script."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
|
|
|
|||
121
dist/ba_data/python/bacommon/text.py
vendored
Normal file
121
dist/ba_data/python/bacommon/text.py
vendored
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Text related bits."""
|
||||
|
||||
from __future__ import annotations
|
||||
from enum import Enum
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
|
||||
class SpecialChar(Enum):
|
||||
"""Custom unicode characters the engine can display.
|
||||
|
||||
Keep this in sync with babase._mgen.enums.SpecialChar.
|
||||
"""
|
||||
|
||||
LEFT_ARROW = '\ue001'
|
||||
RIGHT_ARROW = '\ue002'
|
||||
UP_ARROW = '\ue003'
|
||||
DOWN_ARROW = '\ue004'
|
||||
LEFT_BUTTON = '\ue005'
|
||||
TOP_BUTTON = '\ue006'
|
||||
RIGHT_BUTTON = '\ue007'
|
||||
BOTTOM_BUTTON = '\ue008'
|
||||
DELETE = '\ue009'
|
||||
SHIFT = '\ue00a'
|
||||
BACK = '\ue00b'
|
||||
LOGO_FLAT = '\ue00c'
|
||||
REWIND_BUTTON = '\ue00d'
|
||||
PLAY_PAUSE_BUTTON = '\ue00e'
|
||||
FAST_FORWARD_BUTTON = '\ue00f'
|
||||
DPAD_CENTER_BUTTON = '\ue010'
|
||||
PLAY_STATION_CROSS_BUTTON = '\ue011'
|
||||
PLAY_STATION_CIRCLE_BUTTON = '\ue012'
|
||||
PLAY_STATION_TRIANGLE_BUTTON = '\ue013'
|
||||
PLAY_STATION_SQUARE_BUTTON = '\ue014'
|
||||
PLAY_BUTTON = '\ue015'
|
||||
PAUSE_BUTTON = '\ue016'
|
||||
CLOSE = '\ue017'
|
||||
OUYA_BUTTON_O = '\ue019'
|
||||
OUYA_BUTTON_U = '\ue01a'
|
||||
OUYA_BUTTON_Y = '\ue01b'
|
||||
OUYA_BUTTON_A = '\ue01c'
|
||||
TOKEN = '\ue01d'
|
||||
LOGO = '\ue01e'
|
||||
TICKET = '\ue01f'
|
||||
GOOGLE_PLAY_GAMES_LOGO = '\ue020'
|
||||
GAME_CENTER_LOGO = '\ue021'
|
||||
DICE_BUTTON1 = '\ue022'
|
||||
DICE_BUTTON2 = '\ue023'
|
||||
DICE_BUTTON3 = '\ue024'
|
||||
DICE_BUTTON4 = '\ue025'
|
||||
GAME_CIRCLE_LOGO = '\ue026'
|
||||
PARTY_ICON = '\ue027'
|
||||
TEST_ACCOUNT = '\ue028'
|
||||
TICKET_BACKING = '\ue029'
|
||||
TROPHY1 = '\ue02a'
|
||||
TROPHY2 = '\ue02b'
|
||||
TROPHY3 = '\ue02c'
|
||||
TROPHY0A = '\ue02d'
|
||||
TROPHY0B = '\ue02e'
|
||||
TROPHY4 = '\ue02f'
|
||||
LOCAL_ACCOUNT = '\ue030'
|
||||
EXPLODINARY_LOGO = '\ue031'
|
||||
FLAG_UNITED_STATES = '\ue032'
|
||||
FLAG_MEXICO = '\ue033'
|
||||
FLAG_GERMANY = '\ue034'
|
||||
FLAG_BRAZIL = '\ue035'
|
||||
FLAG_RUSSIA = '\ue036'
|
||||
FLAG_CHINA = '\ue037'
|
||||
FLAG_UNITED_KINGDOM = '\ue038'
|
||||
FLAG_CANADA = '\ue039'
|
||||
FLAG_INDIA = '\ue03a'
|
||||
FLAG_JAPAN = '\ue03b'
|
||||
FLAG_FRANCE = '\ue03c'
|
||||
FLAG_INDONESIA = '\ue03d'
|
||||
FLAG_ITALY = '\ue03e'
|
||||
FLAG_SOUTH_KOREA = '\ue03f'
|
||||
FLAG_NETHERLANDS = '\ue040'
|
||||
FEDORA = '\ue041'
|
||||
HAL = '\ue042'
|
||||
CROWN = '\ue043'
|
||||
YIN_YANG = '\ue044'
|
||||
EYE_BALL = '\ue045'
|
||||
SKULL = '\ue046'
|
||||
HEART = '\ue047'
|
||||
DRAGON = '\ue048'
|
||||
HELMET = '\ue049'
|
||||
MUSHROOM = '\ue04a'
|
||||
NINJA_STAR = '\ue04b'
|
||||
VIKING_HELMET = '\ue04c'
|
||||
MOON = '\ue04d'
|
||||
SPIDER = '\ue04e'
|
||||
FIREBALL = '\ue04f'
|
||||
FLAG_UNITED_ARAB_EMIRATES = '\ue050'
|
||||
FLAG_QATAR = '\ue051'
|
||||
FLAG_EGYPT = '\ue052'
|
||||
FLAG_KUWAIT = '\ue053'
|
||||
FLAG_ALGERIA = '\ue054'
|
||||
FLAG_SAUDI_ARABIA = '\ue055'
|
||||
FLAG_MALAYSIA = '\ue056'
|
||||
FLAG_CZECH_REPUBLIC = '\ue057'
|
||||
FLAG_AUSTRALIA = '\ue058'
|
||||
FLAG_SINGAPORE = '\ue059'
|
||||
OCULUS_LOGO = '\ue05a'
|
||||
STEAM_LOGO = '\ue05b'
|
||||
NVIDIA_LOGO = '\ue05c'
|
||||
FLAG_IRAN = '\ue05d'
|
||||
FLAG_POLAND = '\ue05e'
|
||||
FLAG_ARGENTINA = '\ue05f'
|
||||
FLAG_PHILIPPINES = '\ue060'
|
||||
FLAG_CHILE = '\ue061'
|
||||
MIKIROG = '\ue062'
|
||||
V2_LOGO = '\ue063'
|
||||
SANTA_HAT = '\ue064'
|
||||
POTATO = '\ue065'
|
||||
PALM_TREE = '\ue066'
|
||||
BOXING_GLOVE = '\ue067'
|
||||
8
dist/ba_data/python/bacommon/transfer.py
vendored
8
dist/ba_data/python/bacommon/transfer.py
vendored
|
|
@ -1,6 +1,12 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to transferring files/data."""
|
||||
"""Functionality related to transferring files/data.
|
||||
|
||||
.. warning::
|
||||
|
||||
This is an internal api and subject to change at any time. Do not use
|
||||
it in mod code.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
#
|
||||
"""Public types for assets-v1 workspaces.
|
||||
|
||||
These types may only be used server-side, but they are exposed here
|
||||
for reference when setting workspace config data by hand or for use
|
||||
in client-side workspace modification tools. There may be advanced
|
||||
settings that are not accessible through the UI/etc.
|
||||
While this module is currently only used server-side, its source code
|
||||
can be useful as reference when setting workspace config data by hand or
|
||||
for use in client-side workspace modification tools. There may be
|
||||
advanced settings that are not accessible through the UI/etc.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
@ -18,7 +18,6 @@ from typing import TYPE_CHECKING, Annotated, override, assert_never
|
|||
from efro.dataclassio import ioprepped, IOAttrs, IOMultiType
|
||||
from bacommon.locale import Locale
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
|
|
@ -85,8 +84,8 @@ class AssetsV1StringFileV1(AssetsV1StringFile):
|
|||
|
||||
NONE = 'none'
|
||||
TITLE = 'title'
|
||||
INTENSE = 'intense'
|
||||
SUBTLE = 'subtle'
|
||||
LOUD = 'loud'
|
||||
SOFT = 'soft'
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
|
|
@ -121,7 +120,7 @@ class AssetsV1PathValsTypeID(Enum):
|
|||
"""Types of vals we can store for paths."""
|
||||
|
||||
TEX_V1 = 'tex_v1'
|
||||
# STR_V1 = 'str_v1'
|
||||
STR_V1 = 'str_v1'
|
||||
|
||||
|
||||
class AssetsV1PathVals(IOMultiType[AssetsV1PathValsTypeID]):
|
||||
|
|
@ -151,6 +150,9 @@ class AssetsV1PathVals(IOMultiType[AssetsV1PathValsTypeID]):
|
|||
if type_id is t.TEX_V1:
|
||||
return AssetsV1PathValsTexV1
|
||||
|
||||
if type_id is t.STR_V1:
|
||||
return AssetsV1PathValsStrV1
|
||||
|
||||
# Important to make sure we provide all types.
|
||||
assert_never(type_id)
|
||||
|
||||
|
|
@ -176,3 +178,20 @@ class AssetsV1PathValsTexV1(AssetsV1PathVals):
|
|||
@classmethod
|
||||
def get_type_id(cls) -> AssetsV1PathValsTypeID:
|
||||
return AssetsV1PathValsTypeID.TEX_V1
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class AssetsV1PathValsStrV1(AssetsV1PathVals):
|
||||
"""Path-specific values for an assets_v1 workspace path."""
|
||||
|
||||
#: Hash generated when all translations for this entry are complete.
|
||||
#: Used as a fast-out for checking whether updates are needed.
|
||||
up_to_date_state: Annotated[
|
||||
str | None, IOAttrs('up_to_date_state', store_default=False)
|
||||
] = None
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> AssetsV1PathValsTypeID:
|
||||
return AssetsV1PathValsTypeID.STR_V1
|
||||
|
|
|
|||
7
dist/ba_data/python/baenv.py
vendored
7
dist/ba_data/python/baenv.py
vendored
|
|
@ -14,6 +14,7 @@ Ballistica can be used without explicitly configuring the environment in
|
|||
order to integrate it in arbitrary Python environments, but this may
|
||||
cause some features to be disabled or behave differently than expected.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
|
@ -56,8 +57,8 @@ logger = logging.getLogger('ba.env')
|
|||
|
||||
# Build number and version of the ballistica binary we expect to be
|
||||
# using.
|
||||
TARGET_BALLISTICA_BUILD = 22584
|
||||
TARGET_BALLISTICA_VERSION = '1.7.53'
|
||||
TARGET_BALLISTICA_BUILD = 22712
|
||||
TARGET_BALLISTICA_VERSION = '1.7.61'
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
@ -95,7 +96,7 @@ class EnvConfig:
|
|||
#: stderr into the engine so they show up on in-app consoles, etc.
|
||||
log_handler: LogHandler | None
|
||||
|
||||
# Initial data from the ``config.json`` file in the config dir.
|
||||
#: Initial data from the ``config.json`` file in the config dir.
|
||||
initial_app_config: Any
|
||||
|
||||
#: Timestamp when we first started doing stuff.
|
||||
|
|
|
|||
1
dist/ba_data/python/baplus/_ads.py
vendored
1
dist/ba_data/python/baplus/_ads.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to ads."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
|
|
|
|||
11
dist/ba_data/python/baplus/_appsubsystem.py
vendored
11
dist/ba_data/python/baplus/_appsubsystem.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Provides plus app subsystem."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, override
|
||||
|
|
@ -13,7 +14,7 @@ from baplus._ads import AdsSubsystem
|
|||
if TYPE_CHECKING:
|
||||
from typing import Callable, Any
|
||||
|
||||
import bacommon.bs
|
||||
import bacommon.classic
|
||||
from babase import AccountV2Subsystem
|
||||
|
||||
from baplus._cloud import CloudSubsystem
|
||||
|
|
@ -142,14 +143,6 @@ class PlusAppSubsystem(AppSubsystem):
|
|||
""":meta private:"""
|
||||
return _baplus.get_v1_account_state_num()
|
||||
|
||||
# @staticmethod
|
||||
# def get_v1_account_ticket_count() -> int:
|
||||
# """Return the number of tickets for the current account.
|
||||
|
||||
# :meta private:
|
||||
# """
|
||||
# return _baplus.get_v1_account_ticket_count()
|
||||
|
||||
@staticmethod
|
||||
def get_v1_account_type() -> str:
|
||||
""":meta private:"""
|
||||
|
|
|
|||
94
dist/ba_data/python/baplus/_cloud.py
vendored
94
dist/ba_data/python/baplus/_cloud.py
vendored
|
|
@ -11,7 +11,7 @@ from typing import TYPE_CHECKING, overload
|
|||
from efro.error import CommunicationError
|
||||
from efro.call import CallbackSet
|
||||
from efro.dataclassio import dataclass_from_dict, dataclass_to_dict
|
||||
import bacommon.bs
|
||||
import bacommon.classic
|
||||
import bacommon.cloud
|
||||
import babase
|
||||
|
||||
|
|
@ -19,7 +19,8 @@ if TYPE_CHECKING:
|
|||
from typing import Callable, Any
|
||||
|
||||
from efro.message import Message, Response, BoolResponse
|
||||
import bacommon.bs
|
||||
import bacommon.classic
|
||||
import bacommon.clouddialog as cdlg
|
||||
|
||||
|
||||
# TODO: Should make it possible to define a protocol in bacommon.cloud and
|
||||
|
|
@ -36,6 +37,8 @@ class CloudSubsystem(babase.AppSubsystem):
|
|||
"""
|
||||
|
||||
#: General engine config values provided by the cloud.
|
||||
#:
|
||||
#: :meta private:
|
||||
vals: bacommon.cloud.CloudVals
|
||||
|
||||
def __init__(self) -> None:
|
||||
|
|
@ -214,9 +217,9 @@ class CloudSubsystem(babase.AppSubsystem):
|
|||
@overload
|
||||
def send_message_cb(
|
||||
self,
|
||||
msg: bacommon.bs.GetClassicPurchasesMessage,
|
||||
msg: bacommon.classic.GetClassicPurchasesMessage,
|
||||
on_response: Callable[
|
||||
[bacommon.bs.GetClassicPurchasesResponse | Exception], None
|
||||
[bacommon.classic.GetClassicPurchasesResponse | Exception], None
|
||||
],
|
||||
) -> None: ...
|
||||
|
||||
|
|
@ -232,61 +235,59 @@ class CloudSubsystem(babase.AppSubsystem):
|
|||
@overload
|
||||
def send_message_cb(
|
||||
self,
|
||||
msg: bacommon.bs.PrivatePartyMessage,
|
||||
msg: bacommon.classic.PrivatePartyMessage,
|
||||
on_response: Callable[
|
||||
[bacommon.bs.PrivatePartyResponse | Exception], None
|
||||
[bacommon.classic.PrivatePartyResponse | Exception], None
|
||||
],
|
||||
) -> None: ...
|
||||
|
||||
@overload
|
||||
def send_message_cb(
|
||||
self,
|
||||
msg: bacommon.bs.InboxRequestMessage,
|
||||
msg: bacommon.classic.InboxRequestMessage,
|
||||
on_response: Callable[
|
||||
[bacommon.bs.InboxRequestResponse | Exception], None
|
||||
[bacommon.classic.InboxRequestResponse | Exception], None
|
||||
],
|
||||
) -> None: ...
|
||||
|
||||
@overload
|
||||
def send_message_cb(
|
||||
self,
|
||||
msg: bacommon.bs.CloudDialogActionMessage,
|
||||
msg: cdlg.ActionMessage,
|
||||
on_response: Callable[[cdlg.ActionResponse | Exception], None],
|
||||
) -> None: ...
|
||||
|
||||
@overload
|
||||
def send_message_cb(
|
||||
self,
|
||||
msg: bacommon.classic.ChestInfoMessage,
|
||||
on_response: Callable[
|
||||
[bacommon.bs.CloudDialogActionResponse | Exception], None
|
||||
[bacommon.classic.ChestInfoResponse | Exception], None
|
||||
],
|
||||
) -> None: ...
|
||||
|
||||
@overload
|
||||
def send_message_cb(
|
||||
self,
|
||||
msg: bacommon.bs.ChestInfoMessage,
|
||||
msg: bacommon.cloud.ChestActionMessage,
|
||||
on_response: Callable[
|
||||
[bacommon.bs.ChestInfoResponse | Exception], None
|
||||
[bacommon.cloud.ChestActionResponse | Exception], None
|
||||
],
|
||||
) -> None: ...
|
||||
|
||||
@overload
|
||||
def send_message_cb(
|
||||
self,
|
||||
msg: bacommon.bs.ChestActionMessage,
|
||||
on_response: Callable[
|
||||
[bacommon.bs.ChestActionResponse | Exception], None
|
||||
],
|
||||
) -> None: ...
|
||||
|
||||
@overload
|
||||
def send_message_cb(
|
||||
self,
|
||||
msg: bacommon.bs.GlobalProfileCheckMessage,
|
||||
msg: bacommon.classic.GlobalProfileCheckMessage,
|
||||
on_response: Callable[[BoolResponse | Exception], None],
|
||||
) -> None: ...
|
||||
|
||||
@overload
|
||||
def send_message_cb(
|
||||
self,
|
||||
msg: bacommon.bs.ScoreSubmitMessage,
|
||||
msg: bacommon.classic.ScoreSubmitMessage,
|
||||
on_response: Callable[
|
||||
[bacommon.bs.ScoreSubmitResponse | Exception], None
|
||||
[bacommon.classic.ScoreSubmitResponse | Exception], None
|
||||
],
|
||||
) -> None: ...
|
||||
|
||||
|
|
@ -308,6 +309,29 @@ class CloudSubsystem(babase.AppSubsystem):
|
|||
],
|
||||
) -> None: ...
|
||||
|
||||
@overload
|
||||
def send_message_cb(
|
||||
self,
|
||||
msg: bacommon.classic.GetClassicLeaguePresidentButtonInfoMessage,
|
||||
on_response: Callable[
|
||||
[
|
||||
bacommon.classic.GetClassicLeaguePresidentButtonInfoResponse
|
||||
| Exception
|
||||
],
|
||||
None,
|
||||
],
|
||||
) -> None: ...
|
||||
|
||||
@overload
|
||||
def send_message_cb(
|
||||
self,
|
||||
msg: bacommon.cloud.AnalyticsEventMessage,
|
||||
on_response: Callable[
|
||||
[None | Exception],
|
||||
None,
|
||||
],
|
||||
) -> None: ...
|
||||
|
||||
def send_message_cb(
|
||||
self,
|
||||
msg: Message,
|
||||
|
|
@ -339,8 +363,13 @@ class CloudSubsystem(babase.AppSubsystem):
|
|||
|
||||
@overload
|
||||
def send_message(
|
||||
self, msg: bacommon.bs.LegacyRequest
|
||||
) -> bacommon.bs.LegacyResponse: ...
|
||||
self, msg: bacommon.classic.LegacyRequest
|
||||
) -> bacommon.classic.LegacyResponse: ...
|
||||
|
||||
@overload
|
||||
def send_message(
|
||||
self, msg: bacommon.cloud.FulfillDocUIRequest
|
||||
) -> bacommon.cloud.FulfillDocUIResponse: ...
|
||||
|
||||
def send_message(self, msg: Message) -> Response | None:
|
||||
"""Synchronously send a message to the cloud.
|
||||
|
|
@ -353,8 +382,8 @@ class CloudSubsystem(babase.AppSubsystem):
|
|||
|
||||
@overload
|
||||
async def send_message_async(
|
||||
self, msg: bacommon.bs.SendInfoMessage
|
||||
) -> bacommon.bs.SendInfoResponse: ...
|
||||
self, msg: bacommon.classic.SendInfoMessage
|
||||
) -> bacommon.classic.SendInfoResponse: ...
|
||||
|
||||
@overload
|
||||
async def send_message_async(
|
||||
|
|
@ -383,9 +412,14 @@ class CloudSubsystem(babase.AppSubsystem):
|
|||
|
||||
def subscribe_classic_account_data(
|
||||
self,
|
||||
updatecall: Callable[[bacommon.bs.ClassicAccountLiveData], None],
|
||||
updatecall: Callable[
|
||||
[bacommon.classic.ClassicLiveAccountClientData], None
|
||||
],
|
||||
) -> babase.CloudSubscription:
|
||||
"""Subscribe to classic account data."""
|
||||
"""Subscribe to classic account data.
|
||||
|
||||
:meta private:
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
'Cloud functionality is not present in this build.'
|
||||
)
|
||||
|
|
|
|||
1
dist/ba_data/python/baplus/_hooks.py
vendored
1
dist/ba_data/python/baplus/_hooks.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Snippets of code for use by the c++ layer."""
|
||||
|
||||
# (most of these are self-explanatory)
|
||||
# pylint: disable=missing-function-docstring
|
||||
from __future__ import annotations
|
||||
|
|
|
|||
15
dist/ba_data/python/bascenev1/__init__.py
vendored
15
dist/ba_data/python/bascenev1/__init__.py
vendored
|
|
@ -16,7 +16,6 @@ import logging
|
|||
# other modules; the goal is to let most simple mods rely solely on this
|
||||
# module to keep things simple.
|
||||
|
||||
# from efro.util import set_canonical_module_names
|
||||
from babase import (
|
||||
ActivityNotFoundError,
|
||||
add_clean_frame_callback,
|
||||
|
|
@ -32,6 +31,8 @@ from babase import (
|
|||
apptimer,
|
||||
AppTimer,
|
||||
Call,
|
||||
CallPartial,
|
||||
CallStrict,
|
||||
ContextError,
|
||||
ContextRef,
|
||||
displaytime,
|
||||
|
|
@ -63,6 +64,8 @@ from babase import (
|
|||
unlock_all_input,
|
||||
Vec3,
|
||||
WeakCall,
|
||||
WeakCallPartial,
|
||||
WeakCallStrict,
|
||||
)
|
||||
|
||||
from _bascenev1 import (
|
||||
|
|
@ -275,6 +278,8 @@ __all__ = [
|
|||
'BaseTimer',
|
||||
'BoolSetting',
|
||||
'Call',
|
||||
'CallPartial',
|
||||
'CallStrict',
|
||||
'cameraflash',
|
||||
'camerashake',
|
||||
'Campaign',
|
||||
|
|
@ -475,15 +480,11 @@ __all__ = [
|
|||
'unlock_all_input',
|
||||
'Vec3',
|
||||
'WeakCall',
|
||||
'WeakCallPartial',
|
||||
'WeakCallStrict',
|
||||
'WinnerGroup',
|
||||
]
|
||||
|
||||
# We want stuff here to show up as bascenev1.Foo instead of
|
||||
# bascenev1._submodule.Foo.
|
||||
# UPDATE: Trying without this for now. Seems like this might cause more
|
||||
# harm than good. Can flip it back on if it is missed.
|
||||
# set_canonical_module_names(globals())
|
||||
|
||||
# Sanity check: we want to keep ballistica's dependencies and
|
||||
# bootstrapping order clearly defined; let's check a few particular
|
||||
# modules to make sure they never directly or indirectly import us
|
||||
|
|
|
|||
10
dist/ba_data/python/bascenev1/_activity.py
vendored
10
dist/ba_data/python/bascenev1/_activity.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Defines Activity class."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import weakref
|
||||
|
|
@ -12,9 +13,8 @@ import _bascenev1
|
|||
from bascenev1._dependency import DependencyComponent
|
||||
from bascenev1._messages import UNHANDLED
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
from typing import Any, Self
|
||||
import bascenev1
|
||||
|
||||
|
||||
|
|
@ -192,7 +192,7 @@ class Activity[PlayerT: bascenev1.Player, TeamT: bascenev1.Team](
|
|||
session = self._session()
|
||||
if session is not None:
|
||||
babase.pushcall(
|
||||
babase.Call(
|
||||
babase.CallStrict(
|
||||
session.transitioning_out_activity_was_freed,
|
||||
self.can_show_ad_on_death,
|
||||
)
|
||||
|
|
@ -286,7 +286,7 @@ class Activity[PlayerT: bascenev1.Player, TeamT: bascenev1.Team](
|
|||
ref = weakref.ref(self)
|
||||
self._activity_death_check_timer = babase.AppTimer(
|
||||
5.0,
|
||||
babase.Call(self._check_activity_death, ref, [0]),
|
||||
babase.CallStrict(self._check_activity_death, ref, [0]),
|
||||
repeat=True,
|
||||
)
|
||||
|
||||
|
|
@ -722,7 +722,7 @@ class Activity[PlayerT: bascenev1.Player, TeamT: bascenev1.Team](
|
|||
|
||||
@classmethod
|
||||
def _check_activity_death(
|
||||
cls, activity_ref: weakref.ref[Activity], counter: list[int]
|
||||
cls, activity_ref: weakref.ref[Self], counter: list[int]
|
||||
) -> None:
|
||||
"""Sanity check to make sure an Activity was destroyed properly.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Some handy base class and special purpose Activity types."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, override
|
||||
|
|
@ -14,7 +15,6 @@ from bascenev1._player import EmptyPlayer
|
|||
from bascenev1._team import EmptyTeam
|
||||
from bascenev1._music import MusicType, setmusic
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import bascenev1
|
||||
from bascenev1._lobby import JoinInfo
|
||||
|
|
@ -54,7 +54,7 @@ class EndSessionActivity(Activity[EmptyPlayer, EmptyTeam]):
|
|||
babase.unlock_all_input()
|
||||
assert babase.app.plus is not None
|
||||
|
||||
call = babase.Call(_bascenev1.new_host_session, main_menu_session)
|
||||
call = babase.CallStrict(_bascenev1.new_host_session, main_menu_session)
|
||||
if classic.can_show_interstitial():
|
||||
plus.ads.call_after_ad(call)
|
||||
else:
|
||||
|
|
@ -172,7 +172,7 @@ class ScoreScreenActivity(Activity[EmptyPlayer, EmptyTeam]):
|
|||
# If we're still kicking at the end of our assign-delay, assign this
|
||||
# guy's input to trigger us.
|
||||
_bascenev1.timer(
|
||||
time_till_assign, babase.WeakCall(self._safe_assign, player)
|
||||
time_till_assign, babase.WeakCallStrict(self._safe_assign, player)
|
||||
)
|
||||
|
||||
@override
|
||||
|
|
|
|||
1
dist/ba_data/python/bascenev1/_campaign.py
vendored
1
dist/ba_data/python/bascenev1/_campaign.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to co-op campaigns."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
|
|
|||
8
dist/ba_data/python/bascenev1/_collision.py
vendored
8
dist/ba_data/python/bascenev1/_collision.py
vendored
|
|
@ -19,7 +19,9 @@ class Collision:
|
|||
@property
|
||||
def position(self) -> bascenev1.Vec3:
|
||||
"""The position of the current collision."""
|
||||
return babase.Vec3(_bascenev1.get_collision_info('position'))
|
||||
out = babase.Vec3(_bascenev1.get_collision_info('position'))
|
||||
assert isinstance(out, babase.Vec3)
|
||||
return out
|
||||
|
||||
@property
|
||||
def sourcenode(self) -> bascenev1.Node:
|
||||
|
|
@ -30,7 +32,7 @@ class Collision:
|
|||
start of the collision callback).
|
||||
"""
|
||||
node = _bascenev1.get_collision_info('sourcenode')
|
||||
assert isinstance(node, (_bascenev1.Node, type(None)))
|
||||
assert isinstance(node, _bascenev1.Node | None)
|
||||
if not node:
|
||||
raise babase.NodeNotFoundError()
|
||||
return node
|
||||
|
|
@ -45,7 +47,7 @@ class Collision:
|
|||
currently-colliding node.
|
||||
"""
|
||||
node = _bascenev1.get_collision_info('opposingnode')
|
||||
assert isinstance(node, (_bascenev1.Node, type(None)))
|
||||
assert isinstance(node, _bascenev1.Node | None)
|
||||
if not node:
|
||||
raise babase.NodeNotFoundError()
|
||||
return node
|
||||
|
|
|
|||
7
dist/ba_data/python/bascenev1/_coopgame.py
vendored
7
dist/ba_data/python/bascenev1/_coopgame.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to co-op games."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
|
@ -64,11 +65,11 @@ class CoopGameActivity[PlayerT: bascenev1.Player, TeamT: bascenev1.Team](
|
|||
|
||||
if not arcade_or_demo:
|
||||
_bascenev1.timer(
|
||||
3.8, babase.WeakCall(self._show_remaining_achievements)
|
||||
3.8, babase.WeakCallStrict(self._show_remaining_achievements)
|
||||
)
|
||||
|
||||
# Preload achievement images in case we get some.
|
||||
_bascenev1.timer(2.0, babase.WeakCall(self._preload_achievements))
|
||||
_bascenev1.timer(2.0, babase.WeakCallStrict(self._preload_achievements))
|
||||
|
||||
# FIXME: this is now redundant with activityutils.getscoreconfig();
|
||||
# need to kill this.
|
||||
|
|
@ -232,7 +233,7 @@ class CoopGameActivity[PlayerT: bascenev1.Player, TeamT: bascenev1.Team](
|
|||
"""Set up a beeping noise to play when any players are near death."""
|
||||
self._life_warning_beep = None
|
||||
self._life_warning_beep_timer = _bascenev1.Timer(
|
||||
1.0, babase.WeakCall(self._update_life_warning), repeat=True
|
||||
1.0, babase.WeakCallStrict(self._update_life_warning), repeat=True
|
||||
)
|
||||
|
||||
def _update_life_warning(self) -> None:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to coop-mode sessions."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, override
|
||||
|
|
@ -185,7 +186,9 @@ class CoopSession(Session):
|
|||
def on_player_leave(self, sessionplayer: bascenev1.SessionPlayer) -> None:
|
||||
super().on_player_leave(sessionplayer)
|
||||
|
||||
_bascenev1.timer(2.0, babase.WeakCall(self._handle_empty_activity))
|
||||
_bascenev1.timer(
|
||||
2.0, babase.WeakCallStrict(self._handle_empty_activity)
|
||||
)
|
||||
|
||||
def _handle_empty_activity(self) -> None:
|
||||
"""Handle cases where all players have left the current activity."""
|
||||
|
|
@ -358,7 +361,7 @@ class CoopSession(Session):
|
|||
{
|
||||
'label': babase.Lstr(resource='restartText'),
|
||||
'resume_on_call': False,
|
||||
'call': babase.WeakCall(
|
||||
'call': babase.WeakCallPartial(
|
||||
self._on_tournament_restart_menu_press
|
||||
),
|
||||
}
|
||||
|
|
@ -367,7 +370,7 @@ class CoopSession(Session):
|
|||
self._custom_menu_ui = [
|
||||
{
|
||||
'label': babase.Lstr(resource='restartText'),
|
||||
'call': babase.WeakCall(self.restart),
|
||||
'call': babase.WeakCallStrict(self.restart),
|
||||
}
|
||||
]
|
||||
|
||||
|
|
|
|||
1
dist/ba_data/python/bascenev1/_debug.py
vendored
1
dist/ba_data/python/bascenev1/_debug.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Debugging functionality."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to teams sessions."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, override
|
||||
|
|
|
|||
19
dist/ba_data/python/bascenev1/_gameactivity.py
vendored
19
dist/ba_data/python/bascenev1/_gameactivity.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Provides GameActivity class."""
|
||||
|
||||
# pylint: disable=too-many-lines
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
@ -405,7 +406,9 @@ class GameActivity[PlayerT: bascenev1.Player, TeamT: bascenev1.Team](
|
|||
'tournamentIDs': [tournament_id],
|
||||
'source': 'in-game time remaining query',
|
||||
},
|
||||
callback=babase.WeakCall(self._on_tournament_query_response),
|
||||
callback=babase.WeakCallPartial(
|
||||
self._on_tournament_query_response
|
||||
),
|
||||
)
|
||||
|
||||
def _on_tournament_query_response(
|
||||
|
|
@ -805,7 +808,7 @@ class GameActivity[PlayerT: bascenev1.Player, TeamT: bascenev1.Team](
|
|||
|
||||
player.customdata['respawn_timer'] = _bascenev1.Timer(
|
||||
respawn_time,
|
||||
babase.WeakCall(self.spawn_player_if_exists, player),
|
||||
babase.WeakCallStrict(self.spawn_player_if_exists, player),
|
||||
)
|
||||
player.customdata['respawn_icon'] = RespawnIcon(
|
||||
player, respawn_time
|
||||
|
|
@ -902,7 +905,7 @@ class GameActivity[PlayerT: bascenev1.Player, TeamT: bascenev1.Team](
|
|||
|
||||
self._powerup_drop_timer = _bascenev1.Timer(
|
||||
DEFAULT_POWERUP_INTERVAL,
|
||||
babase.WeakCall(self._standard_drop_powerups),
|
||||
babase.WeakCallStrict(self._standard_drop_powerups),
|
||||
repeat=True,
|
||||
)
|
||||
self._standard_drop_powerups()
|
||||
|
|
@ -927,7 +930,7 @@ class GameActivity[PlayerT: bascenev1.Player, TeamT: bascenev1.Team](
|
|||
points = self.map.powerup_spawn_points
|
||||
for i in range(len(points)):
|
||||
_bascenev1.timer(
|
||||
i * 0.4, babase.WeakCall(self._standard_drop_powerup, i)
|
||||
i * 0.4, babase.WeakCallStrict(self._standard_drop_powerup, i)
|
||||
)
|
||||
|
||||
def _setup_standard_tnt_drops(self) -> None:
|
||||
|
|
@ -953,7 +956,9 @@ class GameActivity[PlayerT: bascenev1.Player, TeamT: bascenev1.Team](
|
|||
return
|
||||
self._standard_time_limit_time = int(duration)
|
||||
self._standard_time_limit_timer = _bascenev1.Timer(
|
||||
1.0, babase.WeakCall(self._standard_time_limit_tick), repeat=True
|
||||
1.0,
|
||||
babase.WeakCallStrict(self._standard_time_limit_tick),
|
||||
repeat=True,
|
||||
)
|
||||
self._standard_time_limit_text = NodeActor(
|
||||
_bascenev1.newnode(
|
||||
|
|
@ -1043,7 +1048,9 @@ class GameActivity[PlayerT: bascenev1.Player, TeamT: bascenev1.Team](
|
|||
# then we have to mess with contexts and whatnot since its currently
|
||||
# not available in activity contexts. :-/
|
||||
self._tournament_time_limit_timer = _bascenev1.BaseTimer(
|
||||
1.0, babase.WeakCall(self._tournament_time_limit_tick), repeat=True
|
||||
1.0,
|
||||
babase.WeakCallStrict(self._tournament_time_limit_tick),
|
||||
repeat=True,
|
||||
)
|
||||
self._tournament_time_limit_title_text = NodeActor(
|
||||
_bascenev1.newnode(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to game results."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
|
|
|
|||
1
dist/ba_data/python/bascenev1/_hooks.py
vendored
1
dist/ba_data/python/bascenev1/_hooks.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Snippets of code for use by the c++ layer."""
|
||||
|
||||
# (most of these are self-explanatory)
|
||||
# pylint: disable=missing-function-docstring
|
||||
from __future__ import annotations
|
||||
|
|
|
|||
8
dist/ba_data/python/bascenev1/_level.py
vendored
8
dist/ba_data/python/bascenev1/_level.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to individual levels in a campaign."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
|
|
@ -118,10 +119,11 @@ class Level:
|
|||
def get_high_scores(self) -> dict:
|
||||
"""Return the current high scores for this level."""
|
||||
config = self._get_config_dict()
|
||||
high_scores_key = 'High Scores' + self.get_score_version_string()
|
||||
if high_scores_key not in config:
|
||||
high_scores_key = f'High Scores{self.get_score_version_string()}'
|
||||
val = config.get(high_scores_key)
|
||||
if isinstance(val, dict):
|
||||
return copy.deepcopy(val)
|
||||
return {}
|
||||
return copy.deepcopy(config[high_scores_key])
|
||||
|
||||
def set_high_scores(self, high_scores: dict) -> None:
|
||||
"""Set high scores for this level."""
|
||||
|
|
|
|||
25
dist/ba_data/python/bascenev1/_lobby.py
vendored
25
dist/ba_data/python/bascenev1/_lobby.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Implements lobby system for gathering before games, char select, etc."""
|
||||
|
||||
# pylint: disable=too-many-lines
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
@ -118,7 +119,7 @@ class JoinInfo:
|
|||
)
|
||||
|
||||
self._timer = _bascenev1.Timer(
|
||||
4.0, babase.WeakCall(self._update), repeat=True
|
||||
4.0, babase.WeakCallStrict(self._update), repeat=True
|
||||
)
|
||||
|
||||
def _update_for_keyboard(self, keyboard: bascenev1.InputDevice) -> None:
|
||||
|
|
@ -609,25 +610,29 @@ class Chooser:
|
|||
if not ready:
|
||||
self._sessionplayer.assigninput(
|
||||
babase.InputType.LEFT_PRESS,
|
||||
babase.Call(self.handlemessage, ChangeMessage('team', -1)),
|
||||
babase.CallStrict(
|
||||
self.handlemessage, ChangeMessage('team', -1)
|
||||
),
|
||||
)
|
||||
self._sessionplayer.assigninput(
|
||||
babase.InputType.RIGHT_PRESS,
|
||||
babase.Call(self.handlemessage, ChangeMessage('team', 1)),
|
||||
babase.CallStrict(self.handlemessage, ChangeMessage('team', 1)),
|
||||
)
|
||||
self._sessionplayer.assigninput(
|
||||
babase.InputType.BOMB_PRESS,
|
||||
babase.Call(self.handlemessage, ChangeMessage('character', 1)),
|
||||
babase.CallStrict(
|
||||
self.handlemessage, ChangeMessage('character', 1)
|
||||
),
|
||||
)
|
||||
self._sessionplayer.assigninput(
|
||||
babase.InputType.UP_PRESS,
|
||||
babase.Call(
|
||||
babase.CallStrict(
|
||||
self.handlemessage, ChangeMessage('profileindex', -1)
|
||||
),
|
||||
)
|
||||
self._sessionplayer.assigninput(
|
||||
babase.InputType.DOWN_PRESS,
|
||||
babase.Call(
|
||||
babase.CallStrict(
|
||||
self.handlemessage, ChangeMessage('profileindex', 1)
|
||||
),
|
||||
)
|
||||
|
|
@ -637,7 +642,9 @@ class Chooser:
|
|||
babase.InputType.PICK_UP_PRESS,
|
||||
babase.InputType.PUNCH_PRESS,
|
||||
),
|
||||
babase.Call(self.handlemessage, ChangeMessage('ready', 1)),
|
||||
babase.CallStrict(
|
||||
self.handlemessage, ChangeMessage('ready', 1)
|
||||
),
|
||||
)
|
||||
self._ready = False
|
||||
self._update_text()
|
||||
|
|
@ -662,7 +669,9 @@ class Chooser:
|
|||
babase.InputType.PICK_UP_PRESS,
|
||||
babase.InputType.PUNCH_PRESS,
|
||||
),
|
||||
babase.Call(self.handlemessage, ChangeMessage('ready', 0)),
|
||||
babase.CallStrict(
|
||||
self.handlemessage, ChangeMessage('ready', 0)
|
||||
),
|
||||
)
|
||||
|
||||
# Store the last profile picked by this input for reuse.
|
||||
|
|
|
|||
1
dist/ba_data/python/bascenev1/_map.py
vendored
1
dist/ba_data/python/bascenev1/_map.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Map related functionality."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
|
|
|
|||
2
dist/ba_data/python/bascenev1/_messages.py
vendored
2
dist/ba_data/python/bascenev1/_messages.py
vendored
|
|
@ -99,7 +99,7 @@ class PlayerDiedMessage:
|
|||
|
||||
Pass the Player type being used by the current game.
|
||||
"""
|
||||
assert isinstance(self._killerplayer, (playertype, type(None)))
|
||||
assert isinstance(self._killerplayer, playertype | None)
|
||||
return self._killerplayer
|
||||
|
||||
def getplayer[PlayerT: bascenev1.Player](
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to teams sessions."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
|
|
|
|||
1
dist/ba_data/python/bascenev1/_net.py
vendored
1
dist/ba_data/python/bascenev1/_net.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to net play."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
|
|
|||
5
dist/ba_data/python/bascenev1/_player.py
vendored
5
dist/ba_data/python/bascenev1/_player.py
vendored
|
|
@ -35,7 +35,8 @@ class StandLocation:
|
|||
angle: float | None = None
|
||||
|
||||
|
||||
class Player[TeamT: bascenev1.Team]:
|
||||
# class Player[TeamT: bascenev1.Team]:
|
||||
class Player[TeamT]:
|
||||
"""A player in a specific bascenev1.Activity.
|
||||
|
||||
These correspond to bascenev1.SessionPlayer objects, but are associated
|
||||
|
|
@ -315,5 +316,5 @@ def playercast_o[PlayerT: bascenev1.Player](
|
|||
totype: type[PlayerT], player: bascenev1.Player | None
|
||||
) -> PlayerT | None:
|
||||
"""A variant of bascenev1.playercast() for optional Player values."""
|
||||
assert isinstance(player, (totype, type(None)))
|
||||
assert isinstance(player, totype | None)
|
||||
return player
|
||||
|
|
|
|||
1
dist/ba_data/python/bascenev1/_profile.py
vendored
1
dist/ba_data/python/bascenev1/_profile.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to player profiles."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
|
|
|
|||
11
dist/ba_data/python/bascenev1/_session.py
vendored
11
dist/ba_data/python/bascenev1/_session.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Defines base session class."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
|
|
@ -338,7 +339,9 @@ class Session:
|
|||
with babase.ContextRef.empty():
|
||||
self._waitlist_timers[identifier] = babase.AppTimer(
|
||||
_g_player_rejoin_cooldown,
|
||||
babase.Call(self._remove_player_from_waitlist, identifier),
|
||||
babase.CallStrict(
|
||||
self._remove_player_from_waitlist, identifier
|
||||
),
|
||||
)
|
||||
|
||||
if not sessionplayer.in_game:
|
||||
|
|
@ -372,7 +375,7 @@ class Session:
|
|||
|
||||
# Grab their activity-specific player instance.
|
||||
player = sessionplayer.activityplayer
|
||||
assert isinstance(player, (Player, type(None)))
|
||||
assert isinstance(player, Player | None)
|
||||
|
||||
# Remove them from any current Activity.
|
||||
if player is not None and activity is not None:
|
||||
|
|
@ -498,7 +501,9 @@ class Session:
|
|||
# Set a timer to set in motion this activity's demise.
|
||||
self._activity_end_timer = _bascenev1.BaseTimer(
|
||||
delay,
|
||||
babase.Call(self._complete_end_activity, activity, results),
|
||||
babase.CallStrict(
|
||||
self._complete_end_activity, activity, results
|
||||
),
|
||||
)
|
||||
|
||||
def handlemessage(self, msg: Any) -> Any:
|
||||
|
|
|
|||
4
dist/ba_data/python/bascenev1/_stats.py
vendored
4
dist/ba_data/python/bascenev1/_stats.py
vendored
|
|
@ -1,6 +1,7 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to scores and statistics."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
|
|
@ -13,7 +14,6 @@ import babase
|
|||
|
||||
import _bascenev1
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Sequence
|
||||
|
||||
|
|
@ -237,7 +237,7 @@ class PlayerRecord:
|
|||
if name is not None:
|
||||
_bascenev1.timer(
|
||||
0.3 + delay,
|
||||
babase.Call(
|
||||
babase.CallStrict(
|
||||
_apply, name, score, showpoints, color, scale, sound
|
||||
),
|
||||
)
|
||||
|
|
|
|||
3
dist/ba_data/python/bascenev1/_team.py
vendored
3
dist/ba_data/python/bascenev1/_team.py
vendored
|
|
@ -65,7 +65,8 @@ class SessionTeam:
|
|||
self.customdata = {}
|
||||
|
||||
|
||||
class Team[PlayerT: bascenev1.Player]:
|
||||
# class Team[PlayerT: bascenev1.Player]:
|
||||
class Team[PlayerT]:
|
||||
"""A team in a specific :class:`~bascenev1.Activity`.
|
||||
|
||||
These correspond to :class:`~bascenev1.SessionTeam` objects, but are
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue