updating ba_data to 1.7.35

This commit is contained in:
Ayush Saini 2024-05-19 18:25:43 +05:30
parent d6e457c821
commit ae30ed15ec
234 changed files with 5670 additions and 2718 deletions

View file

@ -79,6 +79,11 @@ from _babase import (
native_review_request_supported,
native_stack_trace,
open_file_externally,
open_url,
overlay_web_browser_close,
overlay_web_browser_is_open,
overlay_web_browser_is_supported,
overlay_web_browser_open_url,
print_load_info,
pushcall,
quit,
@ -285,6 +290,11 @@ __all__ = [
'normalized_color',
'NotFoundError',
'open_file_externally',
'open_url',
'overlay_web_browser_close',
'overlay_web_browser_is_open',
'overlay_web_browser_is_supported',
'overlay_web_browser_open_url',
'Permission',
'PlayerNotFoundError',
'Plugin',

View file

@ -7,11 +7,11 @@ from __future__ import annotations
import os
import logging
from enum import Enum
from typing import TYPE_CHECKING, TypeVar
from typing import TYPE_CHECKING, TypeVar, override
from concurrent.futures import ThreadPoolExecutor
from functools import cached_property
from threading import RLock
from typing_extensions import override
from efro.call import tpartial
import _babase
@ -220,6 +220,13 @@ class App:
]
self._pool_thread_count = 0
# We hold a lock while lazy-loading our subsystem properties so
# we don't spin up any subsystem more than once, but the lock is
# recursive so that the subsystems can instantiate other
# subsystems.
self._subsystem_property_lock = RLock()
self._subsystem_property_data: dict[str, AppSubsystem | bool] = {}
def postinit(self) -> None:
"""Called after we've been inited and assigned to babase.app.
@ -227,8 +234,9 @@ class App:
must go here instead of __init__.
"""
# Hack for docs-generation: we can be imported with dummy modules
# instead of our actual binary ones, but we don't function.
# Hack for docs-generation: We can be imported with dummy
# modules instead of our actual binary ones, but we don't
# function.
if os.environ.get('BA_RUNNING_WITH_DUMMY_MODULES') == '1':
return
@ -272,10 +280,7 @@ class App:
return self._asyncio_loop
def create_async_task(
self,
coro: Generator[Any, Any, T] | Coroutine[Any, Any, T],
*,
name: str | None = None,
self, coro: Coroutine[Any, Any, T], *, name: str | None = None
) -> None:
"""Create a fully managed async task.
@ -285,6 +290,7 @@ class App:
App.asyncio_loop.
"""
assert _babase.in_logic_thread()
# Hold a strong reference to the task until it is done.
# Otherwise it is possible for it to be garbage collected and
# disappear midway if the caller does not hold on to the
@ -293,7 +299,6 @@ class App:
task = self.asyncio_loop.create_task(coro, name=name)
self._asyncio_tasks.add(task)
task.add_done_callback(self._on_task_done)
# return task
def _on_task_done(self, task: asyncio.Task) -> None:
# Report any errors that occurred.
@ -333,14 +338,66 @@ class App:
def mode_selector(self, selector: babase.AppModeSelector) -> None:
self._mode_selector = selector
def _get_subsystem_property(
self, ssname: str, create_call: Callable[[], AppSubsystem | None]
) -> AppSubsystem | None:
# Quick-out: if a subsystem is present, just return it; no
# locking necessary.
val = self._subsystem_property_data.get(ssname)
if val is not None:
if val is False:
# False means subsystem is confirmed as unavailable.
return None
if val is not True:
# A subsystem has been set. Return it.
return val
# Anything else (no val present or val True) requires locking.
with self._subsystem_property_lock:
val = self._subsystem_property_data.get(ssname)
if val is not None:
if val is False:
# False means confirmed as not present.
return None
if val is True:
# True means this property is already being loaded,
# and the fact that we're holding the lock means
# we're doing the loading, so this is a dependency
# loop. Not good.
raise RuntimeError(
f'Subsystem dependency loop detected for {ssname}'
)
# Must be an instantiated subsystem. Noice.
return val
# Ok, there's nothing here for it. Instantiate and set it
# while we hold the lock. Set a placeholder value of True
# while we load so we can error if something we're loading
# tries to recursively load us.
self._subsystem_property_data[ssname] = True
# Do our one attempt to create the singleton.
val = create_call()
self._subsystem_property_data[ssname] = (
False if val is None else val
)
return val
# __FEATURESET_APP_SUBSYSTEM_PROPERTIES_BEGIN__
# This section generated by batools.appmodule; do not edit.
@cached_property
@property
def classic(self) -> ClassicSubsystem | None:
"""Our classic subsystem (if available)."""
# pylint: disable=cyclic-import
return self._get_subsystem_property(
'classic', self._create_classic_subsystem
) # type: ignore
@staticmethod
def _create_classic_subsystem() -> ClassicSubsystem | None:
# pylint: disable=cyclic-import
try:
from baclassic import ClassicSubsystem
@ -351,11 +408,16 @@ class App:
logging.exception('Error importing baclassic.')
return None
@cached_property
@property
def plus(self) -> PlusSubsystem | None:
"""Our plus subsystem (if available)."""
# pylint: disable=cyclic-import
return self._get_subsystem_property(
'plus', self._create_plus_subsystem
) # type: ignore
@staticmethod
def _create_plus_subsystem() -> PlusSubsystem | None:
# pylint: disable=cyclic-import
try:
from baplus import PlusSubsystem
@ -366,9 +428,15 @@ class App:
logging.exception('Error importing baplus.')
return None
@cached_property
@property
def ui_v1(self) -> UIV1Subsystem:
"""Our ui_v1 subsystem (always available)."""
return self._get_subsystem_property(
'ui_v1', self._create_ui_v1_subsystem
) # type: ignore
@staticmethod
def _create_ui_v1_subsystem() -> UIV1Subsystem:
# pylint: disable=cyclic-import
from bauiv1 import UIV1Subsystem
@ -384,6 +452,7 @@ class App:
# reached the 'running' state. This ensures that all subsystems
# receive a consistent set of callbacks starting with
# on_app_running().
if self._subsystem_registration_ended:
raise RuntimeError(
'Subsystems can no longer be registered at this point.'

View file

@ -8,9 +8,8 @@ import os
import logging
from threading import Thread
from dataclasses import dataclass
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
from efro.call import tpartial
from efro.log import LogLevel
from efro.dataclassio import ioprepped, dataclass_to_json, dataclass_from_json
@ -107,8 +106,8 @@ def handle_v1_cloud_log() -> None:
info = {
'log': _babase.get_v1_cloud_log(),
'version': app.env.version,
'build': app.env.build_number,
'version': app.env.engine_version,
'build': app.env.engine_build_number,
'userAgentString': classic.legacy_user_agent_string,
'session': sessionname,
'activity': activityname,

View file

@ -4,12 +4,10 @@
from __future__ import annotations
import os
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from dataclasses import dataclass
import logging
from typing_extensions import override
import _babase
if TYPE_CHECKING:

View file

@ -3,9 +3,8 @@
"""Provides AppMode functionality."""
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
from bacommon.app import AppExperience
import _babase

View file

@ -7,9 +7,8 @@ import sys
import signal
import logging
import warnings
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
from efro.log import LogLevel
if TYPE_CHECKING:

View file

@ -8,9 +8,8 @@ import weakref
import random
import logging
import inspect
from typing import TYPE_CHECKING, TypeVar, Protocol, NewType
from typing import TYPE_CHECKING, TypeVar, Protocol, NewType, override
from typing_extensions import override
from efro.terminal import Clr
import _babase

View file

@ -85,6 +85,7 @@ def open_url_with_webbrowser_module(url: str) -> None:
import webbrowser
from babase._language import Lstr
assert _babase.in_logic_thread()
try:
webbrowser.open(url)
except Exception:
@ -384,7 +385,7 @@ def show_client_too_old_error() -> None:
# a newer build.
if (
_babase.app.config.get('SuppressClientTooOldErrorForBuild')
== _babase.app.env.build_number
== _babase.app.env.engine_build_number
):
return

View file

@ -6,9 +6,7 @@ from __future__ import annotations
import os
import json
import logging
from typing import TYPE_CHECKING, overload
from typing_extensions import override
from typing import TYPE_CHECKING, overload, override
import _babase
from babase._appsubsystem import AppSubsystem

View file

@ -7,9 +7,8 @@ from __future__ import annotations
import time
import logging
from dataclasses import dataclass
from typing import TYPE_CHECKING, final
from typing import TYPE_CHECKING, final, override
from typing_extensions import override
from bacommon.login import LoginType
import _babase

View file

@ -1,5 +1,5 @@
# Released under the MIT License. See LICENSE for details.
"""Enum vals generated by batools.pythonenumsmodule; do not edit by hand."""
"""Enum vals generated by batools.enumspython; do not edit by hand."""
from enum import Enum
@ -85,39 +85,6 @@ class UIScale(Enum):
SMALL = 2
class TimeType(Enum):
"""Specifies the type of time for various operations to target/use.
Category: Enums
'sim' time is the local simulation time for an activity or session.
It can proceed at different rates depending on game speed, stops
for pauses, etc.
'base' is the baseline time for an activity or session. It proceeds
consistently regardless of game speed or pausing, but may stop during
occurrences such as network outages.
'real' time is mostly based on clock time, with a few exceptions. It may
not advance while the app is backgrounded for instance. (the engine
attempts to prevent single large time jumps from occurring)
"""
SIM = 0
BASE = 1
REAL = 2
class TimeFormat(Enum):
"""Specifies the format time values are provided in.
Category: Enums
"""
SECONDS = 0
MILLISECONDS = 1
class Permission(Enum):
"""Permissions that can be requested from the OS.

View file

@ -32,6 +32,8 @@ class NetworkSubsystem:
# For debugging.
self.v1_test_log: str = ''
self.v1_ctest_results: dict[int, str] = {}
self.connectivity_state = 'uninited'
self.transport_state = 'uninited'
self.server_time_offset_hours: float | None = None
@property

View file

@ -6,9 +6,7 @@ from __future__ import annotations
import logging
import importlib.util
from typing import TYPE_CHECKING
from typing_extensions import override
from typing import TYPE_CHECKING, override
import _babase
from babase._appsubsystem import AppSubsystem

View file

@ -3,9 +3,7 @@
"""UI related bits of babase."""
from __future__ import annotations
from typing import TYPE_CHECKING
from typing_extensions import override
from typing import TYPE_CHECKING, override
from babase._stringedit import StringEditAdapter
import _babase

View file

@ -133,10 +133,15 @@ def create_user_system_scripts() -> None:
if env.python_directory_app is None:
raise RuntimeError('app python dir unset')
path = f'{env.python_directory_user}/sys/{env.version}'
path = f'{env.python_directory_user}/sys/{env.engine_version}'
pathtmp = path + '_tmp'
if os.path.exists(path):
shutil.rmtree(path)
print('Delete Existing User Scripts first!')
_babase.screenmessage(
'Delete Existing User Scripts first!',
color=(1, 0, 0),
)
return
if os.path.exists(pathtmp):
shutil.rmtree(pathtmp)
@ -159,6 +164,7 @@ def create_user_system_scripts() -> None:
f"'\nRestart {_babase.appname()} to use them."
f' (use babase.quit() to exit the game)'
)
_babase.screenmessage('Created User System Scripts', color=(0, 1, 0))
if app.classic is not None and app.classic.platform == 'android':
print(
'Note: the new files may not be visible via '
@ -175,16 +181,18 @@ def delete_user_system_scripts() -> None:
if env.python_directory_user is None:
raise RuntimeError('user python dir unset')
path = f'{env.python_directory_user}/sys/{env.version}'
path = f'{env.python_directory_user}/sys/{env.engine_version}'
if os.path.exists(path):
shutil.rmtree(path)
print(
f'User system scripts deleted.\n'
f'Restart {_babase.appname()} to use internal'
f' scripts. (use babase.quit() to exit the game)'
print('User system scripts deleted.')
_babase.screenmessage('Deleted User System Scripts', color=(0, 1, 0))
_babase.screenmessage(
f'Closing {_babase.appname()} to make changes.', color=(0, 1, 0)
)
_babase.apptimer(2.0, _babase.quit)
else:
print(f"User system scripts not found at '{path}'.")
_babase.screenmessage('User Scripts Not Found', color=(1, 0, 0))
# If the sys path is empty, kill it.
dpath = env.python_directory_user + '/sys'

View file

@ -152,9 +152,9 @@ class AccountV1Subsystem:
"""(internal)"""
for entry in info:
cache_entry = self.tournament_info[
entry['tournamentID']
] = copy.deepcopy(entry)
cache_entry = self.tournament_info[entry['tournamentID']] = (
copy.deepcopy(entry)
)
# Also store the time we received this, so we can adjust
# time-remaining values/etc.

View file

@ -75,9 +75,9 @@ class AchievementSubsystem:
def __init__(self) -> None:
self.achievements: list[Achievement] = []
self.achievements_to_display: (
list[tuple[baclassic.Achievement, bool]]
) = []
self.achievements_to_display: list[
tuple[baclassic.Achievement, bool]
] = []
self.achievement_display_timer: bascenev1.BaseTimer | None = None
self.last_achievement_display_time: float = 0.0
self.achievement_completion_banner_slots: set[int] = set()

View file

@ -5,9 +5,8 @@ from __future__ import annotations
import random
from dataclasses import dataclass
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import babase
import bascenev1
import _baclassic

View file

@ -13,7 +13,10 @@ if TYPE_CHECKING:
def get_input_device_mapped_value(
devicename: str, unique_id: str, name: str
devicename: str,
unique_id: str,
name: str,
default: bool = False,
) -> Any:
"""Returns a mapped value for an input device.
@ -30,8 +33,9 @@ def get_input_device_mapped_value(
subplatform = app.classic.subplatform
appconfig = babase.app.config
# If there's an entry in our config for this controller, use it.
if 'Controllers' in appconfig:
# If there's an entry in our config for this controller and
# we're not looking for our default mappings, use it.
if 'Controllers' in appconfig and not default:
ccfgs = appconfig['Controllers']
if devicename in ccfgs:
mapping = None

View file

@ -7,9 +7,8 @@ import copy
import weakref
import threading
from enum import Enum
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import babase
import bascenev1

View file

@ -102,8 +102,8 @@ class ServerController:
self._shutdown_reason: ShutdownReason | None = None
self._executing_shutdown = False
# Make note if they want us to import a playlist;
# we'll need to do that first if so.
# Make note if they want us to import a playlist; we'll need to
# do that first if so.
self._playlist_fetch_running = self._config.playlist_code is not None
self._playlist_fetch_sent_request = False
self._playlist_fetch_got_response = False
@ -216,7 +216,7 @@ class ServerController:
'bsAccessCheck',
{
'port': bascenev1.get_game_port(),
'b': babase.app.env.build_number,
'b': babase.app.env.engine_build_number,
},
callback=self._access_check_response,
)
@ -304,7 +304,7 @@ class ServerController:
) -> None:
if result is None:
print('Error fetching playlist; aborting.')
print('Falling back to use default playlist.') #BCS
print('Falling back to use default playlist.')
self._config.session_type = "teams"
self._prep_timer = None
babase.pushcall(self._launch_server_session)
@ -314,9 +314,7 @@ class ServerController:
typename = (
'teams'
if result['playlistType'] == 'Team Tournament'
else 'ffa'
if result['playlistType'] == 'Free-for-All'
else '??'
else 'ffa' if result['playlistType'] == 'Free-for-All' else '??'
)
plistname = result['playlistName']
print(f'{Clr.SBLU}Got playlist: "{plistname}" ({typename}).{Clr.RST}')
@ -372,7 +370,8 @@ class ServerController:
raise RuntimeError(f'Unknown session type {sessiontype}')
# Need to add this in a transaction instead of just setting
# it directly or it will get overwritten by the master-server.
# it directly or it will get overwritten by the
# master-server.
plus.add_v1_account_transaction(
{
'type': 'ADD_PLAYLIST',
@ -386,22 +385,23 @@ class ServerController:
if self._first_run:
curtimestr = time.strftime('%c')
startupmsg = (
f'{Clr.BLD}{Clr.BLU}{babase.appnameupper()} {app.env.version}'
f' ({app.env.build_number})'
f'{Clr.BLD}{Clr.BLU}{babase.appnameupper()}'
f' {app.env.engine_version}'
f' ({app.env.engine_build_number})'
f' entering server-mode {curtimestr}{Clr.RST}'
)
logging.info(startupmsg)
if sessiontype is bascenev1.FreeForAllSession:
appcfg['Free-for-All Playlist Selection'] = self._playlist_name
appcfg[
'Free-for-All Playlist Randomize'
] = self._config.playlist_shuffle
appcfg['Free-for-All Playlist Randomize'] = (
self._config.playlist_shuffle
)
elif sessiontype is bascenev1.DualTeamSession:
appcfg['Team Tournament Playlist Selection'] = self._playlist_name
appcfg[
'Team Tournament Playlist Randomize'
] = self._config.playlist_shuffle
appcfg['Team Tournament Playlist Randomize'] = (
self._config.playlist_shuffle
)
elif sessiontype is bascenev1.CoopSession:
classic.coop_session_args = {
'campaign': self._config.coop_campaign,
@ -410,6 +410,10 @@ class ServerController:
else:
raise RuntimeError(f'Unknown session type {sessiontype}')
appcfg['Teams Series Length'] = self._config.teams_series_length
appcfg['FFA Series Length'] = self._config.ffa_series_length
# Deprecated; left here in order to not break mods.
classic.teams_series_length = self._config.teams_series_length
classic.ffa_series_length = self._config.ffa_series_length
@ -425,12 +429,23 @@ class ServerController:
bascenev1.set_public_party_queue_enabled(self._config.enable_queue)
bascenev1.set_public_party_name(self._config.party_name)
bascenev1.set_public_party_stats_url(self._config.stats_url)
bascenev1.set_public_party_public_address_ipv4(
self._config.public_ipv4_address
)
bascenev1.set_public_party_public_address_ipv6(
self._config.public_ipv6_address
)
bascenev1.set_public_party_enabled(self._config.party_is_public)
bascenev1.set_player_rejoin_cooldown(
self._config.player_rejoin_cooldown
)
bascenev1.set_max_players_override(
self._config.session_max_players_override
)
# And here.. we.. go.
if self._config.stress_test_players is not None:
# Special case: run a stress test.

View file

@ -7,6 +7,8 @@ from __future__ import annotations
import logging
from typing import TYPE_CHECKING
from efro.util import utc_now
import babase
import bascenev1
@ -522,10 +524,10 @@ class StoreSubsystem:
if item in sales_raw:
if not plus.get_purchased(item):
to_end = (
datetime.datetime.utcfromtimestamp(
sales_raw[item]['e']
datetime.datetime.fromtimestamp(
sales_raw[item]['e'], datetime.UTC
)
- datetime.datetime.utcnow()
- utc_now()
).total_seconds()
if to_end > 0:
sale_times.append(int(to_end * 1000))

View file

@ -3,12 +3,11 @@
"""Provides classic app subsystem."""
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
import random
import logging
import weakref
from typing_extensions import override
from efro.dataclassio import dataclass_from_dict
import babase
import bauiv1
@ -103,8 +102,8 @@ class ClassicSubsystem(babase.AppSubsystem):
self.maps: dict[str, type[bascenev1.Map]] = {}
# Gameplay.
self.teams_series_length = 7
self.ffa_series_length = 24
self.teams_series_length = 7 # deprecated, left for old mods
self.ffa_series_length = 24 # deprecated, left for old mods
self.coop_session_args: dict = {}
# UI.
@ -575,15 +574,18 @@ class ClassicSubsystem(babase.AppSubsystem):
)
def get_input_device_mapped_value(
self, device: bascenev1.InputDevice, name: str
self,
device: bascenev1.InputDevice,
name: str,
default: bool = False,
) -> Any:
"""Returns a mapped value for an input device.
"""Return a mapped value for an input device.
This checks the user config and falls back to default values
where available.
"""
return _input.get_input_device_mapped_value(
device.name, device.unique_identifier, name
device.name, device.unique_identifier, name, default
)
def get_input_device_map_hash(

View file

@ -35,9 +35,11 @@ def get_tournament_prize_strings(entry: dict[str, Any]) -> list[str]:
prval = (
''
if rng is None
else ('#' + str(rng[0]))
if (rng[0] == rng[1])
else ('#' + str(rng[0]) + '-' + str(rng[1]))
else (
('#' + str(rng[0]))
if (rng[0] == rng[1])
else ('#' + str(rng[0]) + '-' + str(rng[1]))
)
)
pvval = ''
if trophy_type is not None:

View file

@ -6,9 +6,8 @@ from __future__ import annotations
import logging
import threading
from collections import deque
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import babase
from baclassic._music import MusicPlayer

View file

@ -7,9 +7,8 @@ import os
import random
import logging
import threading
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import babase
from baclassic._music import MusicPlayer

View file

@ -15,7 +15,7 @@ if TYPE_CHECKING:
class AppInterfaceIdiom(Enum):
"""A general form-factor or way of experiencing a Ballistica app.
"""A general form-factor or method of experiencing a Ballistica app.
Note that it is possible for a running app to switch idioms (for
instance if a mobile device or computer is connected to a TV).
@ -32,11 +32,11 @@ class AppExperience(Enum):
"""A particular experience that can be provided by a Ballistica app.
This is one metric used to isolate different playerbases from
eachother where there might be no technical barriers doing so.
For example, a casual one-hand-playable phone game and an augmented
each other where there might be no technical barriers doing so. For
example, a casual one-hand-playable phone game and an augmented
reality tabletop game may both use the same scene-versions and
networking-protocols and whatnot, but it would make no sense to
allow players of one join servers for the other. AppExperience can
allow players of one to join servers of the other. AppExperience can
be used to keep these player bases separate.
Generally a single Ballistica app targets a single AppExperience.
@ -47,13 +47,14 @@ class AppExperience(Enum):
visible to client apps designed for that play style.
"""
# An experience that is supported everywhere. Used
# for the default empty AppMode when starting the app, etc.
# An experience that is supported everywhere. Used for the default
# empty AppMode when starting the app, etc.
EMPTY = 'empty'
# The traditional BombSquad experience: multiple players using
# controllers in a single arena small enough for all action to be
# viewed on a single screen.
# traditional game controllers (or touch screen equivalents) in a
# single arena small enough for all action to be viewed on a single
# screen.
MELEE = 'melee'
# The traditional BombSquad Remote experience; buttons on a
@ -72,7 +73,7 @@ class AppArchitecture(Enum):
class AppPlatform(Enum):
"""Overall platform a Ballistica build can be targeting.
"""Overall platform a Ballistica build is targeting.
Each distinct flavor of an app has a unique combination
of AppPlatform and AppVariant. Generally platform describes
@ -124,8 +125,9 @@ class AppInstanceInfo:
"""General info about an individual running app."""
name = Annotated[str, IOAttrs('n')]
version = Annotated[str, IOAttrs('v')]
build = Annotated[int, IOAttrs('b')]
engine_version = Annotated[str, IOAttrs('ev')]
engine_build = Annotated[int, IOAttrs('eb')]
platform = Annotated[AppPlatform, IOAttrs('p')]
variant = Annotated[AppVariant, IOAttrs('va')]

View file

@ -4,10 +4,9 @@
from __future__ import annotations
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Annotated
from typing import TYPE_CHECKING, Annotated, override
from enum import Enum
from typing_extensions import override
from efro.message import Message, Response
from efro.dataclassio import ioprepped, IOAttrs
from bacommon.transfer import DirectoryManifest
@ -33,9 +32,12 @@ class LoginProxyRequestMessage(Message):
class LoginProxyRequestResponse(Response):
"""Response to a request for a login proxy."""
# URL to direct the user to for login.
# URL to direct the user to for sign in.
url: Annotated[str, IOAttrs('u')]
# URL to use for overlay-web-browser sign ins.
url_overlay: Annotated[str, IOAttrs('uo')]
# Proxy-Login id for querying results.
proxyid: Annotated[str, IOAttrs('p')]
@ -123,24 +125,25 @@ class TestResponse(Response):
@ioprepped
@dataclass
class PromoCodeMessage(Message):
"""User is entering a promo code"""
class SendInfoMessage(Message):
"""User is using the send-info function"""
code: Annotated[str, IOAttrs('c')]
description: Annotated[str, IOAttrs('c')]
@override
@classmethod
def get_response_types(cls) -> list[type[Response] | None]:
return [PromoCodeResponse]
return [SendInfoResponse]
@ioprepped
@dataclass
class PromoCodeResponse(Response):
"""Applied that promo code for ya, boss."""
class SendInfoResponse(Response):
"""Response to sending into the server."""
valid: Annotated[bool, IOAttrs('v')]
handled: Annotated[bool, IOAttrs('v')]
message: Annotated[str | None, IOAttrs('m', store_default=False)] = None
legacy_code: Annotated[str | None, IOAttrs('l', store_default=False)] = None
@ioprepped

View file

@ -20,6 +20,11 @@ class ServerNodeEntry:
"""Information about a specific server."""
zone: Annotated[str, IOAttrs('r')]
# TODO: Remove soft_default after all master-servers upgraded.
latlong: Annotated[
tuple[float, float] | None, IOAttrs('ll', soft_default=None)
]
address: Annotated[str, IOAttrs('a')]
port: Annotated[int, IOAttrs('p')]
@ -32,6 +37,16 @@ class ServerNodeQueryResponse:
# The current utc time on the master server.
time: Annotated[datetime.datetime, IOAttrs('t')]
# Where the master server sees the query as coming from.
latlong: Annotated[tuple[float, float] | None, IOAttrs('ll')]
ping_per_dist: Annotated[float, IOAttrs('ppd')]
max_dist: Annotated[float, IOAttrs('md')]
debug_log_seconds: Annotated[
float | None, IOAttrs('d', store_default=False)
] = None
# If present, something went wrong, and this describes it.
error: Annotated[str | None, IOAttrs('e', store_default=False)] = None
@ -78,6 +93,7 @@ class PrivatePartyConnectResult:
"""Info about a server we get back when connecting."""
error: str | None = None
addr: str | None = None
address4: Annotated[str | None, IOAttrs('addr')] = None
address6: Annotated[str | None, IOAttrs('addr6')] = None
port: int | None = None
password: str | None = None

View file

@ -22,109 +22,138 @@ class ServerConfig:
party_name: str = 'FFA'
# If True, your party will show up in the global public party list
# Otherwise it will still be joinable via LAN or connecting by IP address.
# Otherwise it will still be joinable via LAN or connecting by IP
# address.
party_is_public: bool = True
# If True, all connecting clients will be authenticated through the master
# server to screen for fake account info. Generally this should always
# be enabled unless you are hosting on a LAN with no internet connection.
# If True, all connecting clients will be authenticated through the
# master server to screen for fake account info. Generally this
# should always be enabled unless you are hosting on a LAN with no
# internet connection.
authenticate_clients: bool = True
# IDs of server admins. Server admins are not kickable through the default
# kick vote system and they are able to kick players without a vote. To get
# your account id, enter 'getaccountid' in settings->advanced->enter-code.
# IDs of server admins. Server admins are not kickable through the
# default kick vote system and they are able to kick players without
# a vote. To get your account id, enter 'getaccountid' in
# settings->advanced->enter-code.
admins: list[str] = field(default_factory=list)
# Whether the default kick-voting system is enabled.
enable_default_kick_voting: bool = True
# UDP port to host on. Change this to work around firewalls or run multiple
# servers on one machine.
# 43210 is the default and the only port that will show up in the LAN
# browser tab.
# To be included in the public server list, your server MUST be
# accessible via an ipv4 address. By default, the master server will
# try to use the address your server contacts it from, but this may
# be an ipv6 address these days so you may need to provide an ipv4
# address explicitly.
public_ipv4_address: str | None = None
# You can optionally provide an ipv6 address for your server for the
# public server list. Unlike ipv4, a server is not required to have
# an ipv6 address to appear in the list, but is still good to
# provide when available since more and more devices are using ipv6
# these days. Your server's ipv6 address will be autodetected if
# your server uses ipv6 when communicating with the master server. You
# can pass an empty string here to explicitly disable the ipv6
# address.
public_ipv6_address: str | None = None
# UDP port to host on. Change this to work around firewalls or run
# multiple servers on one machine.
#
# 43210 is the default and the only port that will show up in the
# LAN browser tab.
port: int = 43210
# Max devices in the party. Note that this does *NOT* mean max players.
# Any device in the party can have more than one player on it if they have
# multiple controllers. Also, this number currently includes the server so
# generally make it 1 bigger than you need. Max-players is not currently
# exposed but I'll try to add that soon.
# Max devices in the party. Note that this does *NOT* mean max
# players. Any device in the party can have more than one player on
# it if they have multiple controllers. Also, this number currently
# includes the server so generally make it 1 bigger than you need.
max_party_size: int = 6
# Options here are 'ffa' (free-for-all), 'teams' and 'coop' (cooperative)
# This value is ignored if you supply a playlist_code (see below).
# Max players that can join a session. If present this will override
# the session's preferred max_players. if a value below 0 is given
# player limit will be removed.
session_max_players_override: int | None = None
# Options here are 'ffa' (free-for-all), 'teams' and 'coop'
# (cooperative) This value is ignored if you supply a playlist_code
# (see below).
session_type: str = 'ffa'
# Playlist-code for teams or free-for-all mode sessions.
# To host your own custom playlists, use the 'share' functionality in the
# playlist editor in the regular version of the game.
# This will give you a numeric code you can enter here to host that
# playlist.
# Playlist-code for teams or free-for-all mode sessions. To host
# your own custom playlists, use the 'share' functionality in the
# playlist editor in the regular version of the game. This will give
# you a numeric code you can enter here to host that playlist.
playlist_code: int | None = None
# Alternately, you can embed playlist data here instead of using codes.
# Make sure to set session_type to the correct type for the data here.
# Alternately, you can embed playlist data here instead of using
# codes. Make sure to set session_type to the correct type for the
# data here.
playlist_inline: list[dict[str, Any]] | None = None
# Whether to shuffle the playlist or play its games in designated order.
# Whether to shuffle the playlist or play its games in designated
# order.
playlist_shuffle: bool = True
# If True, keeps team sizes equal by disallowing joining the largest team
# (teams mode only).
# If True, keeps team sizes equal by disallowing joining the largest
# team (teams mode only).
auto_balance_teams: bool = True
# The campaign used when in co-op session mode.
# Do print(ba.app.campaigns) to see available campaign names.
# The campaign used when in co-op session mode. Do
# print(ba.app.campaigns) to see available campaign names.
coop_campaign: str = 'Easy'
# The level name within the campaign used in co-op session mode.
# For campaign name FOO, do print(ba.app.campaigns['FOO'].levels) to see
# The level name within the campaign used in co-op session mode. For
# campaign name FOO, do print(ba.app.campaigns['FOO'].levels) to see
# available level names.
coop_level: str = 'Onslaught Training'
# Whether to enable telnet access.
# IMPORTANT: This option is no longer available, as it was being used
# for exploits. Live access to the running server is still possible through
# the mgr.cmd() function in the server script. Run your server through
# tools such as 'screen' or 'tmux' and you can reconnect to it remotely
# over a secure ssh connection.
#
# IMPORTANT: This option is no longer available, as it was being
# used for exploits. Live access to the running server is still
# possible through the mgr.cmd() function in the server script. Run
# your server through tools such as 'screen' or 'tmux' and you can
# reconnect to it remotely over a secure ssh connection.
enable_telnet: bool = False
# Series length in teams mode (7 == 'best-of-7' series; a team must
# get 4 wins)
teams_series_length: int = 7
# Points to win in free-for-all mode (Points are awarded per game based on
# performance)
# Points to win in free-for-all mode (Points are awarded per game
# based on performance)
ffa_series_length: int = 24
# If you have a custom stats webpage for your server, you can use this
# to provide a convenient in-game link to it in the server-browser
# alongside the server name.
# If you have a custom stats webpage for your server, you can use
# this to provide a convenient in-game link to it in the
# server-browser alongside the server name.
#
# if ${ACCOUNT} is present in the string, it will be replaced by the
# currently-signed-in account's id. To fetch info about an account,
# your back-end server can use the following url:
# https://legacy.ballistica.net/accountquery?id=ACCOUNT_ID_HERE
stats_url: str | None = None
# If present, the server subprocess will attempt to gracefully exit after
# this amount of time. A graceful exit can occur at the end of a series
# or other opportune time. Server-managers set to auto-restart (the
# default) will then spin up a fresh subprocess. This mechanism can be
# useful to clear out any memory leaks or other accumulated bad state
# in the server subprocess.
# If present, the server subprocess will attempt to gracefully exit
# after this amount of time. A graceful exit can occur at the end of
# a series or other opportune time. Server-managers set to
# auto-restart (the default) will then spin up a fresh subprocess.
# This mechanism can be useful to clear out any memory leaks or
# other accumulated bad state in the server subprocess.
clean_exit_minutes: float | None = None
# If present, the server subprocess will shut down immediately after this
# amount of time. This can be useful as a fallback for clean_exit_time.
# The server manager will then spin up a fresh server subprocess if
# auto-restart is enabled (the default).
# If present, the server subprocess will shut down immediately after
# this amount of time. This can be useful as a fallback for
# clean_exit_time. The server manager will then spin up a fresh
# server subprocess if auto-restart is enabled (the default).
unclean_exit_minutes: float | None = None
# If present, the server subprocess will shut down immediately if this
# amount of time passes with no activity from any players. The server
# manager will then spin up a fresh server subprocess if auto-restart is
# enabled (the default).
# If present, the server subprocess will shut down immediately if
# this amount of time passes with no activity from any players. The
# server manager will then spin up a fresh server subprocess if
# auto-restart is enabled (the default).
idle_exit_minutes: float | None = None
# Should the tutorial be shown at the beginning of games?
@ -138,9 +167,9 @@ class ServerConfig:
tuple[tuple[float, float, float], tuple[float, float, float]] | None
) = None
# Whether to enable the queue where players can line up before entering
# your server. Disabling this can be used as a workaround to deal with
# queue spamming attacks.
# Whether to enable the queue where players can line up before
# entering your server. Disabling this can be used as a workaround
# to deal with queue spamming attacks.
enable_queue: bool = True
# Protocol version we host with. Currently the default is 33 which
@ -158,9 +187,9 @@ class ServerConfig:
player_rejoin_cooldown: float = 10.0
# NOTE: as much as possible, communication from the server-manager to the
# child-process should go through these and not ad-hoc Python string commands
# since this way is type safe.
# NOTE: as much as possible, communication from the server-manager to
# the child-process should go through these and not ad-hoc Python string
# commands since this way is type safe.
class ServerCommand:
"""Base class for commands that can be sent to the server."""

View file

@ -31,7 +31,9 @@ class DirectoryManifest:
files: Annotated[dict[str, DirectoryManifestFile], IOAttrs('f')]
# _empty_hash: str | None = None
# Soft-default added April 2024; can remove eventually once this
# attr is widespread in client.
exists: Annotated[bool, IOAttrs('e', soft_default=True)]
@classmethod
def create_from_disk(cls, path: Path) -> DirectoryManifest:
@ -42,6 +44,8 @@ class DirectoryManifest:
pathstr = str(path)
paths: list[str] = []
exists = path.exists()
if path.is_dir():
# Build the full list of relative paths.
for basename, _dirnames, filenames in os.walk(path):
@ -51,7 +55,7 @@ class DirectoryManifest:
# Make sure we end up with forward slashes no matter
# what the os.* stuff above here was using.
paths.append(Path(fullname[len(pathstr) + 1 :]).as_posix())
elif path.exists():
elif exists:
# Just return a single file entry if path is not a dir.
paths.append(path.as_posix())
@ -76,7 +80,9 @@ class DirectoryManifest:
if cpus is None:
cpus = 4
with ThreadPoolExecutor(max_workers=cpus) as executor:
return cls(files=dict(executor.map(_get_file_info, paths)))
return cls(
files=dict(executor.map(_get_file_info, paths)), exists=exists
)
def validate(self) -> None:
"""Log any odd data in the manifest; for debugging."""

View file

@ -52,8 +52,8 @@ if TYPE_CHECKING:
# Build number and version of the ballistica binary we expect to be
# using.
TARGET_BALLISTICA_BUILD = 21766
TARGET_BALLISTICA_VERSION = '1.7.33'
TARGET_BALLISTICA_BUILD = 21879
TARGET_BALLISTICA_VERSION = '1.7.35'
@dataclass
@ -264,6 +264,10 @@ def _calc_data_dir(data_dir: str | None) -> str:
def _setup_logging() -> LogHandler:
from efro.log import setup_logging, LogLevel
# TODO: should set this up with individual loggers under a top level
# 'ba' logger, and at that point we can kill off the
# suppress_non_root_debug option since we'll only ever need to set
# 'ba' to DEBUG at most.
log_handler = setup_logging(
log_path=None,
level=LogLevel.DEBUG,

View file

@ -57,8 +57,7 @@ class CloudSubsystem(babase.AppSubsystem):
on_response: Callable[
[bacommon.cloud.LoginProxyRequestResponse | Exception], None
],
) -> None:
...
) -> None: ...
@overload
def send_message_cb(
@ -67,24 +66,21 @@ class CloudSubsystem(babase.AppSubsystem):
on_response: Callable[
[bacommon.cloud.LoginProxyStateQueryResponse | Exception], None
],
) -> None:
...
) -> None: ...
@overload
def send_message_cb(
self,
msg: bacommon.cloud.LoginProxyCompleteMessage,
on_response: Callable[[None | Exception], None],
) -> None:
...
) -> None: ...
@overload
def send_message_cb(
self,
msg: bacommon.cloud.PingMessage,
on_response: Callable[[bacommon.cloud.PingResponse | Exception], None],
) -> None:
...
) -> None: ...
@overload
def send_message_cb(
@ -93,8 +89,7 @@ class CloudSubsystem(babase.AppSubsystem):
on_response: Callable[
[bacommon.cloud.SignInResponse | Exception], None
],
) -> None:
...
) -> None: ...
@overload
def send_message_cb(
@ -103,8 +98,7 @@ class CloudSubsystem(babase.AppSubsystem):
on_response: Callable[
[bacommon.cloud.ManageAccountResponse | Exception], None
],
) -> None:
...
) -> None: ...
def send_message_cb(
self,
@ -129,20 +123,17 @@ class CloudSubsystem(babase.AppSubsystem):
@overload
def send_message(
self, msg: bacommon.cloud.WorkspaceFetchMessage
) -> bacommon.cloud.WorkspaceFetchResponse:
...
) -> bacommon.cloud.WorkspaceFetchResponse: ...
@overload
def send_message(
self, msg: bacommon.cloud.MerchAvailabilityMessage
) -> bacommon.cloud.MerchAvailabilityResponse:
...
) -> bacommon.cloud.MerchAvailabilityResponse: ...
@overload
def send_message(
self, msg: bacommon.cloud.TestMessage
) -> bacommon.cloud.TestResponse:
...
) -> bacommon.cloud.TestResponse: ...
def send_message(self, msg: Message) -> Response | None:
"""Synchronously send a message to the cloud.
@ -153,15 +144,13 @@ class CloudSubsystem(babase.AppSubsystem):
@overload
async def send_message_async(
self, msg: bacommon.cloud.PromoCodeMessage
) -> bacommon.cloud.PromoCodeResponse:
...
self, msg: bacommon.cloud.SendInfoMessage
) -> bacommon.cloud.SendInfoResponse: ...
@overload
async def send_message_async(
self, msg: bacommon.cloud.TestMessage
) -> bacommon.cloud.TestResponse:
...
) -> bacommon.cloud.TestResponse: ...
async def send_message_async(self, msg: Message) -> Response | None:
"""Synchronously send a message to the cloud.

View file

@ -3,9 +3,8 @@
"""Provides plus app subsystem."""
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
from babase import AppSubsystem
import _baplus

View file

@ -120,6 +120,7 @@ from _bascenev1 import (
release_keyboard_input,
reset_random_player_names,
resume_replay,
seek_replay,
broadcastmessage,
SessionData,
SessionPlayer,
@ -133,6 +134,8 @@ from _bascenev1 import (
set_public_party_enabled,
set_public_party_max_size,
set_public_party_name,
set_public_party_public_address_ipv4,
set_public_party_public_address_ipv6,
set_public_party_queue_enabled,
set_public_party_stats_url,
set_replay_speed_exponent,
@ -231,7 +234,11 @@ from bascenev1._settings import (
IntSetting,
Setting,
)
from bascenev1._session import Session, set_player_rejoin_cooldown
from bascenev1._session import (
Session,
set_player_rejoin_cooldown,
set_max_players_override,
)
from bascenev1._stats import PlayerScoredMessage, PlayerRecord, Stats
from bascenev1._team import SessionTeam, Team, EmptyTeam
from bascenev1._teamgame import TeamGameActivity
@ -400,6 +407,7 @@ __all__ = [
'release_keyboard_input',
'reset_random_player_names',
'resume_replay',
'seek_replay',
'safecolor',
'screenmessage',
'SceneV1AppMode',
@ -423,9 +431,12 @@ __all__ = [
'set_public_party_enabled',
'set_public_party_max_size',
'set_public_party_name',
'set_public_party_public_address_ipv4',
'set_public_party_public_address_ipv6',
'set_public_party_queue_enabled',
'set_public_party_stats_url',
'set_player_rejoin_cooldown',
'set_max_players_override',
'set_replay_speed_exponent',
'set_touchscreen_editing',
'setmusic',

View file

@ -3,9 +3,8 @@
"""Some handy base class and special purpose Activity types."""
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import babase
import _bascenev1
@ -203,9 +202,11 @@ class ScoreScreenActivity(Activity[EmptyPlayer, EmptyTeam]):
sval = babase.Lstr(resource='pressAnyButtonText')
Text(
self._custom_continue_message
if self._custom_continue_message is not None
else sval,
(
self._custom_continue_message
if self._custom_continue_message is not None
else sval
),
v_attach=Text.VAttach.BOTTOM,
h_align=Text.HAlign.CENTER,
flash=True,

View file

@ -198,12 +198,14 @@ class Actor:
# Overloads to convey our exact return type depending on 'doraise' value.
@overload
def getactivity(self, doraise: Literal[True] = True) -> bascenev1.Activity:
...
def getactivity(
self, doraise: Literal[True] = True
) -> bascenev1.Activity: ...
@overload
def getactivity(self, doraise: Literal[False]) -> bascenev1.Activity | None:
...
def getactivity(
self, doraise: Literal[False]
) -> bascenev1.Activity | None: ...
def getactivity(self, doraise: bool = True) -> bascenev1.Activity | None:
"""Return the bascenev1.Activity this Actor is associated with.

View file

@ -3,9 +3,8 @@
"""Provides AppMode functionality."""
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
from bacommon.app import AppExperience
from babase import (
app,

View file

@ -4,9 +4,8 @@
from __future__ import annotations
import logging
from typing import TYPE_CHECKING, TypeVar
from typing import TYPE_CHECKING, TypeVar, override
from typing_extensions import override
import babase
import _bascenev1

View file

@ -3,9 +3,8 @@
"""Functionality related to coop-mode sessions."""
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import babase
import _bascenev1
@ -61,6 +60,10 @@ class CoopSession(Session):
max_players = classic.coop_session_args['max_players']
else:
max_players = app.config.get('Coop Game Max Players', 4)
if 'submit_score' in classic.coop_session_args:
submit_score = classic.coop_session_args['submit_score']
else:
submit_score = True
# print('FIXME: COOP SESSION WOULD CALC DEPS.')
depsets: Sequence[bascenev1.DependencySet] = []
@ -71,6 +74,7 @@ class CoopSession(Session):
team_colors=TEAM_COLORS,
min_players=min_players,
max_players=max_players,
submit_score=submit_score,
)
# Tournament-ID if we correspond to a co-op tournament (otherwise None)
@ -346,7 +350,10 @@ class CoopSession(Session):
self.setactivity(next_game)
if not (env.demo or env.arcade):
if self.tournament_id is not None:
if (
self.tournament_id is not None
and classic.coop_session_args['submit_score']
):
self._custom_menu_ui = [
{
'label': babase.Lstr(resource='restartText'),

View file

@ -5,9 +5,8 @@
from __future__ import annotations
import weakref
from typing import Generic, TypeVar, TYPE_CHECKING
from typing import Generic, TypeVar, TYPE_CHECKING, override
from typing_extensions import override
import babase
import _bascenev1

View file

@ -3,9 +3,8 @@
"""Functionality related to teams sessions."""
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import babase
import _bascenev1

View file

@ -4,9 +4,8 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import babase
import _bascenev1

View file

@ -7,9 +7,8 @@ from __future__ import annotations
import random
import logging
from typing import TYPE_CHECKING, TypeVar
from typing import TYPE_CHECKING, TypeVar, override
from typing_extensions import override
import babase
import _bascenev1

View file

@ -42,9 +42,9 @@ class GameResults:
self._scores: dict[
int, tuple[weakref.ref[bascenev1.SessionTeam], int | None]
] = {}
self._sessionteams: list[
weakref.ref[bascenev1.SessionTeam]
] | None = None
self._sessionteams: list[weakref.ref[bascenev1.SessionTeam]] | None = (
None
)
self._playerinfos: list[bascenev1.PlayerInfo] | None = None
self._lower_is_better: bool | None = None
self._score_label: str | None = None

View file

@ -5,9 +5,8 @@ from __future__ import annotations
import copy
import weakref
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import babase
if TYPE_CHECKING:
@ -73,9 +72,11 @@ class Level:
return babase.Lstr(
translate=(
'coopLevelNames',
self._displayname
if self._displayname is not None
else self._name,
(
self._displayname
if self._displayname is not None
else self._name
),
),
subs=[
('${GAME}', self._gametype.get_display_string(self._settings))

View file

@ -4,9 +4,8 @@
from __future__ import annotations
import random
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import babase
import _bascenev1
@ -256,9 +255,7 @@ class Map(Actor):
return (
None
if val is None
else babase.vec3validate(val)
if __debug__
else val
else babase.vec3validate(val) if __debug__ else val
)
def get_def_points(self, name: str) -> list[Sequence[float]]:
@ -334,8 +331,7 @@ class Map(Actor):
closest_player_dist = 9999.0
for ppt in player_pts:
dist = (ppt - testpt).length()
if dist < closest_player_dist:
closest_player_dist = dist
closest_player_dist = min(dist, closest_player_dist)
if closest_player_dist > farthestpt_dist:
farthestpt_dist = closest_player_dist
farthestpt = testpt

View file

@ -6,9 +6,8 @@ from __future__ import annotations
import copy
import random
import logging
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import babase
import _bascenev1
@ -67,8 +66,8 @@ class MultiTeamSession(Session):
max_players=self.get_max_players(),
)
self._series_length: int = classic.teams_series_length
self._ffa_series_length: int = classic.ffa_series_length
self._series_length: int = int(cfg.get('Teams Series Length', 7))
self._ffa_series_length: int = int(cfg.get('FFA Series Length', 24))
show_tutorial = cfg.get('Show Tutorial', True)

View file

@ -4,9 +4,7 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from typing_extensions import override
from typing import TYPE_CHECKING, override
from bascenev1._messages import DieMessage
from bascenev1._actor import Actor

View file

@ -89,18 +89,18 @@ def filter_playlist(
'bs_king_of_the_hill.KingOfTheHillGame',
'bastd.game.kingofthehill.KingOfTheHillGame',
):
entry[
'type'
] = 'bascenev1lib.game.kingofthehill.KingOfTheHillGame'
entry['type'] = (
'bascenev1lib.game.kingofthehill.KingOfTheHillGame'
)
if entry['type'] in (
'Capture_the_Flag.CTFGame',
'bsCaptureTheFlag.CTFGame',
'bs_capture_the_flag.CTFGame',
'bastd.game.capturetheflag.CaptureTheFlagGame',
):
entry[
'type'
] = 'bascenev1lib.game.capturetheflag.CaptureTheFlagGame'
entry['type'] = (
'bascenev1lib.game.capturetheflag.CaptureTheFlagGame'
)
if entry['type'] in (
'Death_Match.DeathMatchGame',
'bsDeathMatch.DeathMatchGame',
@ -163,25 +163,25 @@ def filter_playlist(
'bs_easter_egg_hunt.EasterEggHuntGame',
'bastd.game.easteregghunt.EasterEggHuntGame',
):
entry[
'type'
] = 'bascenev1lib.game.easteregghunt.EasterEggHuntGame'
entry['type'] = (
'bascenev1lib.game.easteregghunt.EasterEggHuntGame'
)
if entry['type'] in (
'bsMeteorShower.MeteorShowerGame',
'bs_meteor_shower.MeteorShowerGame',
'bastd.game.meteorshower.MeteorShowerGame',
):
entry[
'type'
] = 'bascenev1lib.game.meteorshower.MeteorShowerGame'
entry['type'] = (
'bascenev1lib.game.meteorshower.MeteorShowerGame'
)
if entry['type'] in (
'bsTargetPractice.TargetPracticeGame',
'bs_target_practice.TargetPracticeGame',
'bastd.game.targetpractice.TargetPracticeGame',
):
entry[
'type'
] = 'bascenev1lib.game.targetpractice.TargetPracticeGame'
entry['type'] = (
'bascenev1lib.game.targetpractice.TargetPracticeGame'
)
gameclass = babase.getclass(entry['type'], GameActivity)

View file

@ -23,6 +23,9 @@ if TYPE_CHECKING:
# such as skipping respawn waits.
_g_player_rejoin_cooldown: float = 0.0
# overrides the session's decision of max_players
_max_players_override: int | None = None
def set_player_rejoin_cooldown(cooldown: float) -> None:
"""Set the cooldown for individual players rejoining after leaving."""
@ -30,6 +33,12 @@ def set_player_rejoin_cooldown(cooldown: float) -> None:
_g_player_rejoin_cooldown = max(0.0, cooldown)
def set_max_players_override(max_players: int | None) -> None:
"""Set the override for how many players can join a session"""
global _max_players_override # pylint: disable=global-statement
_max_players_override = max_players
class Session:
"""Defines a high level series of bascenev1.Activity-es.
@ -91,6 +100,7 @@ class Session:
team_colors: Sequence[Sequence[float]] | None = None,
min_players: int = 1,
max_players: int = 8,
submit_score: bool = True,
):
"""Instantiate a session.
@ -161,7 +171,12 @@ class Session:
self.sessionteams = []
self.sessionplayers = []
self.min_players = min_players
self.max_players = max_players
self.max_players = (
max_players
if _max_players_override is None
else _max_players_override
)
self.submit_score = submit_score
self.customdata = {}
self._in_set_activity = False
@ -255,7 +270,7 @@ class Session:
babase.app.classic is not None
and babase.app.classic.stress_test_update_timer is None
):
if len(self.sessionplayers) >= self.max_players:
if len(self.sessionplayers) >= self.max_players >= 0:
# Print a rejection message *only* to the client trying to
# join (prevents spamming everyone else in the game).
_bascenev1.getsound('error').play()

View file

@ -5,9 +5,8 @@
from __future__ import annotations
import logging
from typing import TYPE_CHECKING, TypeVar
from typing import TYPE_CHECKING, TypeVar, override
from typing_extensions import override
import babase
import _bascenev1

View file

@ -4,7 +4,8 @@
from __future__ import annotations
from typing_extensions import override
from typing import override
import bascenev1 as bs

View file

@ -7,9 +7,8 @@ from __future__ import annotations
import random
import logging
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
from bacommon.login import LoginType
import bascenev1 as bs
import bauiv1 as bui
@ -125,6 +124,7 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
self._tournament_time_remaining: float | None = None
self._tournament_time_remaining_text: Text | None = None
self._tournament_time_remaining_text_timer: bs.BaseTimer | None = None
self._submit_score = self.session.submit_score
# Stuff for activity skip by pressing button
self._birth_time = bs.time()
@ -395,11 +395,15 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
color=(0.45, 0.4, 0.5),
position=(160, v_offs + 480),
size=(350, 62),
label=bui.Lstr(resource='tournamentStandingsText')
if self.session.tournament_id is not None
else bui.Lstr(resource='worldsBestScoresText')
if self._score_type == 'points'
else bui.Lstr(resource='worldsBestTimesText'),
label=(
bui.Lstr(resource='tournamentStandingsText')
if self.session.tournament_id is not None
else (
bui.Lstr(resource='worldsBestScoresText')
if self._score_type == 'points'
else bui.Lstr(resource='worldsBestTimesText')
)
),
autoselect=True,
on_activate_call=bui.WeakCall(self._ui_worlds_best),
transition_delay=delay + 1.9,
@ -515,9 +519,11 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
bui.containerwidget(
edit=rootc,
selected_child=next_button
if (self._newly_complete and self._victory and show_next_button)
else restart_button,
selected_child=(
next_button
if (self._newly_complete and self._victory and show_next_button)
else restart_button
),
on_cancel_call=menu_button.activate,
)
@ -644,14 +650,16 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
and not (env.demo or env.arcade)
):
Text(
bs.Lstr(
value='${A}:\n',
subs=[('${A}', bs.Lstr(resource='levelUnlockedText'))],
)
if self._newly_complete
else bs.Lstr(
value='${A}:\n',
subs=[('${A}', bs.Lstr(resource='nextLevelText'))],
(
bs.Lstr(
value='${A}:\n',
subs=[('${A}', bs.Lstr(resource='levelUnlockedText'))],
)
if self._newly_complete
else bs.Lstr(
value='${A}:\n',
subs=[('${A}', bs.Lstr(resource='nextLevelText'))],
)
),
transition=Text.Transition.IN_RIGHT,
transition_delay=5.2,
@ -781,7 +789,7 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
transition_delay=2.0,
)
if self._score is not None:
if self._score is not None and self._submit_score:
bs.timer(0.4, bs.WeakCall(self._play_drumroll))
# Add us to high scores, filter, and store.
@ -860,11 +868,15 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
ts_h_offs = 210
v_offs = 40
txt = Text(
bs.Lstr(resource='tournamentStandingsText')
if self.session.tournament_id is not None
else bs.Lstr(resource='worldsBestScoresText')
if self._score_type == 'points'
else bs.Lstr(resource='worldsBestTimesText'),
(
bs.Lstr(resource='tournamentStandingsText')
if self.session.tournament_id is not None
else (
bs.Lstr(resource='worldsBestScoresText')
if self._score_type == 'points'
else bs.Lstr(resource='worldsBestTimesText')
)
),
maxwidth=210,
position=(ts_h_offs - 10, ts_height / 2 + 25 + v_offs + 20),
transition=Text.Transition.IN_LEFT,
@ -882,9 +894,11 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
ts_h_offs = -480
v_offs = 40
Text(
bs.Lstr(resource='yourBestScoresText')
if self._score_type == 'points'
else bs.Lstr(resource='yourBestTimesText'),
(
bs.Lstr(resource='yourBestScoresText')
if self._score_type == 'points'
else bs.Lstr(resource='yourBestTimesText')
),
maxwidth=210,
position=(ts_h_offs - 10, ts_height / 2 + 25 + v_offs + 20),
transition=Text.Transition.IN_RIGHT,
@ -948,9 +962,11 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
tdelay1 = times[i][0]
tdelay2 = times[i][1]
Text(
str(display_scores[i][0])
if self._score_type == 'points'
else bs.timestring((display_scores[i][0] * 10) / 1000.0),
(
str(display_scores[i][0])
if self._score_type == 'points'
else bs.timestring((display_scores[i][0] * 10) / 1000.0)
),
position=(
ts_h_offs + 20 + h_offs_extra,
v_offs_extra
@ -1127,9 +1143,11 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
tdelay2 = times[i][1]
if name_str != '-':
Text(
str(score)
if self._score_type == 'points'
else bs.timestring((score * 10) / 1000.0),
(
str(score)
if self._score_type == 'points'
else bs.timestring((score * 10) / 1000.0)
),
position=(
ts_h_offs + 20 + h_offs_extra,
v_offs_extra
@ -1313,9 +1331,11 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
if name_str != '-':
Text(
str(score)
if self._score_type == 'points'
else bs.timestring((score * 10) / 1000.0),
(
str(score)
if self._score_type == 'points'
else bs.timestring((score * 10) / 1000.0)
),
position=(
ts_h_offs + 20 + h_offs_extra,
ts_height / 2
@ -1376,7 +1396,7 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
assert self._show_info is not None
available = self._show_info['results'] is not None
if available:
if available and self._submit_score:
error = (
self._show_info['results']['error']
if 'error' in self._show_info['results']
@ -1509,7 +1529,7 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
maxwidth=400,
transition_delay=1.0,
).autoretain()
else:
elif self._submit_score:
ZoomText(
(
('#' + str(player_rank))
@ -1689,17 +1709,22 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
)
if not self._newly_complete:
Text(
bs.Lstr(
value='${A}${B}',
subs=[
('${A}', bs.Lstr(resource='newPersonalBestText')),
('${B}', was_string),
],
)
if new_best
else bs.Lstr(
resource='bestRatingText',
subs=[('${RATING}', str(best_rank))],
(
bs.Lstr(
value='${A}${B}',
subs=[
(
'${A}',
bs.Lstr(resource='newPersonalBestText'),
),
('${B}', was_string),
],
)
if new_best
else bs.Lstr(
resource='bestRatingText',
subs=[('${RATING}', str(best_rank))],
)
),
position=(0, -165),
color=(1, 1, 1, 0.7),
@ -1727,9 +1752,10 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
transition_delay=0,
).autoretain()
bs.timer(0.35, self._score_display_sound.play)
if not error:
bs.timer(0.35, self.cymbal_sound.play)
if self._submit_score:
bs.timer(0.35, self._score_display_sound.play)
if not error:
bs.timer(0.35, self.cymbal_sound.play)
def _show_fail(self) -> None:
ZoomText(
@ -1773,14 +1799,16 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
jitter=1.0,
).autoretain()
Text(
bs.Lstr(
value='${A}:',
subs=[('${A}', bs.Lstr(resource='finalScoreText'))],
)
if self._score_type == 'points'
else bs.Lstr(
value='${A}:',
subs=[('${A}', bs.Lstr(resource='finalTimeText'))],
(
bs.Lstr(
value='${A}:',
subs=[('${A}', bs.Lstr(resource='finalScoreText'))],
)
if self._score_type == 'points'
else bs.Lstr(
value='${A}:',
subs=[('${A}', bs.Lstr(resource='finalTimeText'))],
)
),
maxwidth=300,
position=(0, 200),

View file

@ -4,7 +4,8 @@
from __future__ import annotations
from typing_extensions import override
from typing import override
import bascenev1 as bs
from bascenev1lib.activity.multiteamscore import MultiTeamScoreScreenActivity

View file

@ -4,7 +4,8 @@
from __future__ import annotations
from typing_extensions import override
from typing import override
import bascenev1 as bs
from bascenev1lib.activity.multiteamscore import MultiTeamScoreScreenActivity

View file

@ -4,9 +4,8 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import bascenev1 as bs
from bascenev1lib.activity.multiteamscore import MultiTeamScoreScreenActivity

View file

@ -4,7 +4,8 @@
from __future__ import annotations
from typing_extensions import override
from typing import override
import bascenev1 as bs
from bascenev1lib.actor.text import Text

View file

@ -3,7 +3,8 @@
"""Functionality related to teams mode score screen."""
from __future__ import annotations
from typing_extensions import override
from typing import override
import bascenev1 as bs
from bascenev1lib.actor.text import Text
@ -199,9 +200,9 @@ class MultiTeamScoreScreenActivity(bs.ScoreScreenActivity):
ts_v_offset + (voffs + 15) * scale,
),
scale=scale,
color=(1.0, 0.9, 0.5, 1.0)
if highlight
else (0.5, 0.5, 0.6, 0.5),
color=(
(1.0, 0.9, 0.5, 1.0) if highlight else (0.5, 0.5, 0.6, 0.5)
),
h_align=Text.HAlign.RIGHT,
v_align=Text.VAlign.CENTER,
maxwidth=maxwidth,

View file

@ -4,7 +4,8 @@
from __future__ import annotations
from typing_extensions import override
from typing import override
import bascenev1 as bs
from bascenev1lib.activity.multiteamscore import MultiTeamScoreScreenActivity
@ -374,9 +375,11 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
tdelay -= 4 * t_incr
v_offs -= 40
Text(
str(prec.team.customdata['score'])
if self._is_ffa
else str(prec.score),
(
str(prec.team.customdata['score'])
if self._is_ffa
else str(prec.score)
),
color=(0.5, 0.5, 0.5, 1.0),
position=(ts_h_offs + 230, ts_height / 2 + v_offs),
h_align=Text.HAlign.RIGHT,

View file

@ -7,9 +7,8 @@ from __future__ import annotations
import random
import weakref
import logging
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import bascenev1 as bs
if TYPE_CHECKING:

View file

@ -8,9 +8,8 @@
from __future__ import annotations
import random
from typing import TYPE_CHECKING, TypeVar
from typing import TYPE_CHECKING, TypeVar, override
from typing_extensions import override
import bascenev1 as bs
from bascenev1lib.gameutils import SharedObjects

View file

@ -4,9 +4,8 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import bascenev1 as bs
if TYPE_CHECKING:
@ -456,9 +455,11 @@ class ControlsGuide(bs.Actor):
(
'${B}',
bs.Lstr(
resource='holdAnyKeyText'
if all_keyboards
else 'holdAnyButtonText'
resource=(
'holdAnyKeyText'
if all_keyboards
else 'holdAnyButtonText'
)
),
),
],

View file

@ -5,9 +5,8 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import bascenev1 as bs
from bascenev1lib.gameutils import SharedObjects

View file

@ -5,9 +5,8 @@
from __future__ import annotations
from enum import Enum
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import bascenev1 as bs
if TYPE_CHECKING:

View file

@ -4,9 +4,8 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import bascenev1 as bs
if TYPE_CHECKING:

View file

@ -3,10 +3,9 @@
"""Defines Actor(s)."""
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
import logging
from typing_extensions import override
import bascenev1 as bs
if TYPE_CHECKING:

View file

@ -4,9 +4,8 @@
from __future__ import annotations
from typing import TYPE_CHECKING, TypeVar, overload
from typing import TYPE_CHECKING, TypeVar, overload, override
from typing_extensions import override
import bascenev1 as bs
from bascenev1lib.actor.spaz import Spaz
@ -79,14 +78,12 @@ class PlayerSpaz(Spaz):
@overload
def getplayer(
self, playertype: type[PlayerT], doraise: Literal[False] = False
) -> PlayerT | None:
...
) -> PlayerT | None: ...
@overload
def getplayer(
self, playertype: type[PlayerT], doraise: Literal[True]
) -> PlayerT:
...
) -> PlayerT: ...
def getplayer(
self, playertype: type[PlayerT], doraise: bool = False
@ -225,10 +222,20 @@ class PlayerSpaz(Spaz):
elif isinstance(msg, bs.DieMessage):
# Report player deaths to the game.
if not self._dead:
# Immediate-mode or left-game deaths don't count as 'kills'.
killed = (
not msg.immediate and msg.how is not bs.DeathType.LEFT_GAME
# Was this player killed while being held?
was_held = self.held_count > 0 and self.last_player_held_by
# Was this player attacked before death?
was_attacked_recently = (
self.last_player_attacked_by
and bs.time() - self.last_attacked_time < 4.0
)
# Leaving the game doesn't count as a kill *unless*
# someone does it intentionally while being attacked.
left_game_cleanly = msg.how is bs.DeathType.LEFT_GAME and not (
was_held or was_attacked_recently
)
killed = not (msg.immediate or left_game_cleanly)
activity = self._activity()
@ -238,7 +245,7 @@ class PlayerSpaz(Spaz):
else:
# If this player was being held at the time of death,
# the holder is the killer.
if self.held_count > 0 and self.last_player_held_by:
if was_held:
killerplayer = self.last_player_held_by
else:
# Otherwise, if they were attacked by someone in the
@ -248,10 +255,7 @@ class PlayerSpaz(Spaz):
# all bot kills would register as suicides; need to
# change this from last_player_attacked_by to
# something like last_actor_attacked_by to fix that.
if (
self.last_player_attacked_by
and bs.time() - self.last_attacked_time < 4.0
):
if was_attacked_recently:
killerplayer = self.last_player_attacked_by
else:
# ok, call it a suicide unless we're in co-op

View file

@ -5,9 +5,8 @@
from __future__ import annotations
import random
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import bascenev1 as bs
if TYPE_CHECKING:

View file

@ -5,9 +5,8 @@
from __future__ import annotations
import random
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import bascenev1 as bs
from bascenev1lib.gameutils import SharedObjects

View file

@ -22,7 +22,9 @@ class RespawnIcon:
def __init__(self, player: bs.Player, respawn_time: float):
"""Instantiate with a Player and respawn_time (in seconds)."""
# pylint: disable=too-many-locals
self._visible = True
self._dots_epic_only = False
on_right, offs_extra, respawn_icons = self._get_context(player)
@ -92,7 +94,7 @@ class RespawnIcon:
assert self._name.node
bs.animate(self._name.node, 'scale', {0: 0, 0.1: 0.5})
tpos = (-60 - h_offs if on_right else 60 + h_offs, -192 + offs)
tpos = (-60 - h_offs if on_right else 60 + h_offs, -193 + offs)
self._text: bs.NodeActor | None = bs.NodeActor(
bs.newnode(
'text',
@ -109,11 +111,37 @@ class RespawnIcon:
},
)
)
dpos = [ipos[0] + (7 if on_right else -7), ipos[1] - 16]
self._dec_text: bs.NodeActor | None = None
if (
self._dots_epic_only
and bs.getactivity().globalsnode.slow_motion
or not self._dots_epic_only
):
self._dec_text = bs.NodeActor(
bs.newnode(
'text',
attrs={
'position': dpos,
'h_attach': 'right' if on_right else 'left',
'h_align': 'right' if on_right else 'left',
'scale': 0.65,
'shadow': 0.5,
'flatness': 0.5,
'v_attach': 'top',
'color': bs.safecolor(icon['tint_color']),
'text': '',
},
)
)
assert self._text.node
bs.animate(self._text.node, 'scale', {0: 0, 0.1: 0.9})
if self._dec_text:
bs.animate(self._dec_text.node, 'scale', {0: 0, 0.1: 0.65})
self._respawn_time = bs.time() + respawn_time
self._dec_timer: bs.Timer | None = None
self._update()
self._timer: bs.Timer | None = bs.Timer(
1.0, bs.WeakCall(self._update), repeat=True
@ -128,7 +156,7 @@ class RespawnIcon:
"""Return info on where we should be shown and stored."""
activity = bs.getactivity()
if isinstance(bs.getsession(), bs.DualTeamSession):
if isinstance(activity.session, bs.DualTeamSession):
on_right = player.team.id % 2 == 1
# Store a list of icons in the team.
@ -153,12 +181,43 @@ class RespawnIcon:
offs_extra = -20
return on_right, offs_extra, icons
def _dec_step(self, display: list) -> None:
if not self._dec_text:
self._dec_timer = None
return
old_text: bs.Lstr | str = self._dec_text.node.text
iterate: int
# Get the following display text using our current one.
try:
iterate = display.index(old_text) + 1
# If we don't match any in the display list, we
# can assume we've just started iterating.
except ValueError:
iterate = 0
# Kill the timer if we're at the last iteration.
if iterate >= len(display):
self._dec_timer = None
return
self._dec_text.node.text = display[iterate]
def _update(self) -> None:
remaining = int(round(self._respawn_time - bs.time()))
if remaining > 0:
assert self._text is not None
if self._text.node:
self._text.node.text = str(remaining)
if self._dec_text:
# Display our decimal dots.
self._dec_text.node.text = '...'
# Start the timer to tick down.
self._dec_timer = bs.Timer(
0.25,
bs.WeakCall(self._dec_step, ['..', '.', '']),
repeat=True,
)
else:
self._visible = False
self._image = self._text = self._timer = self._name = None
self._image = self._text = self._dec_text = self._timer = (
self._name
) = None

View file

@ -7,13 +7,12 @@ from __future__ import annotations
import random
import logging
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import bascenev1 as bs
from bascenev1lib.actor.bomb import Bomb, Blast
from bascenev1lib.actor.powerupbox import PowerupBoxFactory
from bascenev1lib.actor.powerupbox import PowerupBoxFactory, PowerupBox
from bascenev1lib.actor.spazfactory import SpazFactory
from bascenev1lib.gameutils import SharedObjects
@ -68,6 +67,7 @@ class Spaz(bs.Actor):
default_bomb_type = 'normal'
default_boxing_gloves = False
default_shields = False
default_hitpoints = 1000
def __init__(
self,
@ -174,8 +174,8 @@ class Spaz(bs.Actor):
setattr(node, attr, val)
bs.timer(1.0, bs.Call(_safesetattr, self.node, 'invincible', False))
self.hitpoints = 1000
self.hitpoints_max = 1000
self.hitpoints = self.default_hitpoints
self.hitpoints_max = self.default_hitpoints
self.shield_hitpoints: int | None = None
self.shield_hitpoints_max = 650
self.shield_decay_rate = 0
@ -629,7 +629,8 @@ class Spaz(bs.Actor):
1000.0 * (tval + self.curse_time)
)
self._curse_timer = bs.Timer(
5.0, bs.WeakCall(self.handlemessage, CurseExplodeMessage())
self.curse_time,
bs.WeakCall(self.handlemessage, CurseExplodeMessage()),
)
def equip_boxing_gloves(self) -> None:
@ -1227,6 +1228,10 @@ class Spaz(bs.Actor):
return None
node = bs.getcollision().opposingnode
# Don't want to physically affect powerups.
if node.getdelegate(PowerupBox):
return None
# Only allow one hit per node per punch.
if node and (node not in self._punched_nodes):
punch_momentum_angular = (

View file

@ -8,9 +8,8 @@ from __future__ import annotations
import random
import weakref
import logging
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import bascenev1 as bs
from bascenev1lib.actor.spaz import Spaz
@ -776,7 +775,6 @@ class ChargerBotPro(ChargerBot):
color = PRO_BOT_COLOR
highlight = PRO_BOT_HIGHLIGHT
default_shields = True
default_boxing_gloves = True
points_mult = 3

View file

@ -5,9 +5,8 @@
from __future__ import annotations
from enum import Enum
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import bascenev1 as bs
if TYPE_CHECKING:

View file

@ -4,9 +4,8 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import bascenev1 as bs
if TYPE_CHECKING:
@ -73,9 +72,11 @@ class TipsText(bs.Actor):
next_tip = bs.Lstr(
translate=(
'tips',
bs.app.classic.get_next_tip()
if bs.app.classic is not None
else '',
(
bs.app.classic.get_next_tip()
if bs.app.classic is not None
else ''
),
),
subs=[('${REMOTE_APP_NAME}', get_remote_app_name())],
)

View file

@ -6,9 +6,8 @@ from __future__ import annotations
import random
import logging
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import bascenev1 as bs
if TYPE_CHECKING:

View file

@ -8,9 +8,8 @@
from __future__ import annotations
import random
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import bascenev1 as bs
from bascenev1lib.actor.playerspaz import PlayerSpaz

View file

@ -8,9 +8,8 @@
from __future__ import annotations
import logging
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import bascenev1 as bs
from bascenev1lib.actor.playerspaz import PlayerSpaz
@ -527,7 +526,38 @@ class CaptureTheFlagGame(bs.TeamGameActivity[Player, Team]):
team.touch_return_timer = None
team.touch_return_timer_ticking = None
if team.flag_return_touches < 0:
logging.exception('CTF flag_return_touches < 0')
logging.error('CTF flag_return_touches < 0', stack_info=True)
def _handle_death_flag_capture(self, player: Player) -> None:
"""Handles flag values when a player dies or leaves the game."""
# Don't do anything if the player hasn't touched the flag at all.
if not player.touching_own_flag:
return
team = player.team
# For each "point" our player has touched theflag (Could be
# multiple), deduct one from both our player and the flag's
# return touches variable.
for _ in range(player.touching_own_flag):
# Deduct
player.touching_own_flag -= 1
# (This was only incremented if we have non-zero
# return-times).
if float(self.flag_touch_return_time) > 0.0:
team.flag_return_touches -= 1
# Update our flag's timer accordingly
# (Prevents immediate resets in case
# there might be more people touching it).
if team.flag_return_touches == 0:
team.touch_return_timer = None
team.touch_return_timer_ticking = None
# Safety check, just to be sure!
if team.flag_return_touches < 0:
logging.error(
'CTF flag_return_touches < 0', stack_info=True
)
def _flash_base(self, team: Team, length: float = 2.0) -> None:
light = bs.newnode(
@ -591,6 +621,7 @@ class CaptureTheFlagGame(bs.TeamGameActivity[Player, Team]):
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.PlayerDiedMessage):
super().handlemessage(msg) # Augment standard behavior.
self._handle_death_flag_capture(msg.getplayer(Player))
self.respawn_player(msg.getplayer(Player))
elif isinstance(msg, FlagDiedMessage):
@ -617,3 +648,8 @@ class CaptureTheFlagGame(bs.TeamGameActivity[Player, Team]):
else:
super().handlemessage(msg)
@override
def on_player_leave(self, player: Player) -> None:
"""Prevents leaving players from capturing their flag."""
self._handle_death_flag_capture(player)

View file

@ -8,9 +8,8 @@
from __future__ import annotations
import logging
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import bascenev1 as bs
from bascenev1lib.actor.flag import Flag

View file

@ -8,9 +8,8 @@
from __future__ import annotations
import random
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import bascenev1 as bs
from bascenev1lib.actor.flag import Flag

View file

@ -7,9 +7,8 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import bascenev1 as bs
from bascenev1lib.actor.playerspaz import PlayerSpaz

View file

@ -8,9 +8,8 @@
from __future__ import annotations
import random
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import bascenev1 as bs
from bascenev1lib.actor.bomb import Bomb

View file

@ -8,9 +8,8 @@
from __future__ import annotations
import logging
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import bascenev1 as bs
from bascenev1lib.actor.spazfactory import SpazFactory
@ -478,7 +477,7 @@ class EliminationGame(bs.TeamGameActivity[Player, Team]):
points.append(
((start_pos - player_pos).length(), start_pos)
)
# Hmm.. we need to sorting vectors too?
# Hmm.. we need to sort vectors too?
points.sort(key=lambda x: x[0])
return points[-1][1]
return None

View file

@ -11,9 +11,8 @@ from __future__ import annotations
import math
import random
import logging
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import bascenev1 as bs
from bascenev1lib.actor.bomb import TNTSpawner

View file

@ -7,9 +7,8 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import bascenev1 as bs
from bascenev1lib.actor.playerspaz import PlayerSpaz

View file

@ -9,9 +9,8 @@ from __future__ import annotations
import logging
from enum import Enum
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import bascenev1 as bs
from bascenev1lib.actor.playerspaz import PlayerSpaz

View file

@ -9,9 +9,8 @@ from __future__ import annotations
import weakref
from enum import Enum
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import bascenev1 as bs
from bascenev1lib.actor.flag import Flag

View file

@ -8,9 +8,8 @@
from __future__ import annotations
import random
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import bascenev1 as bs
from bascenev1lib.actor.bomb import Bomb
@ -73,6 +72,7 @@ class MeteorShowerGame(bs.TeamGameActivity[Player, Team]):
self._last_player_death_time: float | None = None
self._meteor_time = 2.0
self._timer: OnScreenTimer | None = None
self._ended: bool = False
# Some base class overrides:
self.default_music = (
@ -161,6 +161,10 @@ class MeteorShowerGame(bs.TeamGameActivity[Player, Team]):
return None
def _check_end_game(self) -> None:
# We don't want to end this activity more than once.
if self._ended:
return
living_team_count = 0
for team in self.teams:
for player in team.players:
@ -270,4 +274,5 @@ class MeteorShowerGame(bs.TeamGameActivity[Player, Team]):
# Submit the score value in milliseconds.
results.set_team_score(team, int(1000.0 * longest_life))
self._ended = True
self.end(results=results)

View file

@ -8,9 +8,8 @@
from __future__ import annotations
import random
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import bascenev1 as bs
from bascenev1lib.actor.spazbot import (

View file

@ -15,9 +15,8 @@ import random
import logging
from enum import Enum, unique
from dataclasses import dataclass
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import bascenev1 as bs
from bascenev1lib.actor.popuptext import PopupText
@ -334,29 +333,37 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]):
Wave(
base_angle=130,
entries=[
Spawn(BrawlerBotLite, spacing=5)
if player_count > 1
else None,
(
Spawn(BrawlerBotLite, spacing=5)
if player_count > 1
else None
),
Spawn(BrawlerBotLite, spacing=5),
Spacing(30),
Spawn(BomberBotLite, spacing=5)
if player_count > 3
else None,
(
Spawn(BomberBotLite, spacing=5)
if player_count > 3
else None
),
Spawn(BomberBotLite, spacing=5),
Spacing(30),
Spawn(BrawlerBotLite, spacing=5),
Spawn(BrawlerBotLite, spacing=5)
if player_count > 2
else None,
(
Spawn(BrawlerBotLite, spacing=5)
if player_count > 2
else None
),
],
),
Wave(
base_angle=195,
entries=[
Spawn(TriggerBot, spacing=90),
Spawn(TriggerBot, spacing=90)
if player_count > 1
else None,
(
Spawn(TriggerBot, spacing=90)
if player_count > 1
else None
),
],
),
]
@ -367,9 +374,11 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]):
self._waves = [
Wave(
entries=[
Spawn(ChargerBot, Point.LEFT_UPPER_MORE)
if player_count > 2
else None,
(
Spawn(ChargerBot, Point.LEFT_UPPER_MORE)
if player_count > 2
else None
),
Spawn(ChargerBot, Point.LEFT_UPPER),
]
),
@ -377,36 +386,50 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]):
entries=[
Spawn(BomberBotStaticLite, Point.TURRET_TOP_RIGHT),
Spawn(BrawlerBotLite, Point.RIGHT_UPPER),
Spawn(BrawlerBotLite, Point.RIGHT_LOWER)
if player_count > 1
else None,
Spawn(BomberBotStaticLite, Point.TURRET_BOTTOM_RIGHT)
if player_count > 2
else None,
(
Spawn(BrawlerBotLite, Point.RIGHT_LOWER)
if player_count > 1
else None
),
(
Spawn(
BomberBotStaticLite, Point.TURRET_BOTTOM_RIGHT
)
if player_count > 2
else None
),
]
),
Wave(
entries=[
Spawn(BomberBotStaticLite, Point.TURRET_BOTTOM_LEFT),
Spawn(TriggerBot, Point.LEFT),
Spawn(TriggerBot, Point.LEFT_LOWER)
if player_count > 1
else None,
Spawn(TriggerBot, Point.LEFT_UPPER)
if player_count > 2
else None,
(
Spawn(TriggerBot, Point.LEFT_LOWER)
if player_count > 1
else None
),
(
Spawn(TriggerBot, Point.LEFT_UPPER)
if player_count > 2
else None
),
]
),
Wave(
entries=[
Spawn(BrawlerBotLite, Point.TOP_RIGHT),
Spawn(BrawlerBot, Point.TOP_HALF_RIGHT)
if player_count > 1
else None,
(
Spawn(BrawlerBot, Point.TOP_HALF_RIGHT)
if player_count > 1
else None
),
Spawn(BrawlerBotLite, Point.TOP_LEFT),
Spawn(BrawlerBotLite, Point.TOP_HALF_LEFT)
if player_count > 2
else None,
(
Spawn(BrawlerBotLite, Point.TOP_HALF_LEFT)
if player_count > 2
else None
),
Spawn(BrawlerBot, Point.TOP),
Spawn(BomberBotStaticLite, Point.TURRET_TOP_MIDDLE),
]
@ -416,12 +439,16 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]):
Spawn(TriggerBotStatic, Point.TURRET_BOTTOM_LEFT),
Spawn(TriggerBotStatic, Point.TURRET_BOTTOM_RIGHT),
Spawn(TriggerBot, Point.BOTTOM),
Spawn(TriggerBot, Point.BOTTOM_HALF_RIGHT)
if player_count > 1
else None,
Spawn(TriggerBot, Point.BOTTOM_HALF_LEFT)
if player_count > 2
else None,
(
Spawn(TriggerBot, Point.BOTTOM_HALF_RIGHT)
if player_count > 1
else None
),
(
Spawn(TriggerBot, Point.BOTTOM_HALF_LEFT)
if player_count > 2
else None
),
]
),
Wave(
@ -429,12 +456,16 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]):
Spawn(BomberBotStaticLite, Point.TURRET_TOP_LEFT),
Spawn(BomberBotStaticLite, Point.TURRET_TOP_RIGHT),
Spawn(ChargerBot, Point.BOTTOM),
Spawn(ChargerBot, Point.BOTTOM_HALF_LEFT)
if player_count > 1
else None,
Spawn(ChargerBot, Point.BOTTOM_HALF_RIGHT)
if player_count > 2
else None,
(
Spawn(ChargerBot, Point.BOTTOM_HALF_LEFT)
if player_count > 1
else None
),
(
Spawn(ChargerBot, Point.BOTTOM_HALF_RIGHT)
if player_count > 2
else None
),
]
),
]
@ -446,44 +477,62 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]):
Wave(
base_angle=-50,
entries=[
Spawn(BrawlerBot, spacing=12)
if player_count > 3
else None,
(
Spawn(BrawlerBot, spacing=12)
if player_count > 3
else None
),
Spawn(BrawlerBot, spacing=12),
Spawn(BomberBot, spacing=6),
Spawn(BomberBot, spacing=6)
if self._preset is Preset.PRO
else None,
Spawn(BomberBot, spacing=6)
if player_count > 1
else None,
(
Spawn(BomberBot, spacing=6)
if self._preset is Preset.PRO
else None
),
(
Spawn(BomberBot, spacing=6)
if player_count > 1
else None
),
Spawn(BrawlerBot, spacing=12),
Spawn(BrawlerBot, spacing=12)
if player_count > 2
else None,
(
Spawn(BrawlerBot, spacing=12)
if player_count > 2
else None
),
],
),
Wave(
base_angle=180,
entries=[
Spawn(BrawlerBot, spacing=6)
if player_count > 3
else None,
Spawn(BrawlerBot, spacing=6)
if self._preset is Preset.PRO
else None,
(
Spawn(BrawlerBot, spacing=6)
if player_count > 3
else None
),
(
Spawn(BrawlerBot, spacing=6)
if self._preset is Preset.PRO
else None
),
Spawn(BrawlerBot, spacing=6),
Spawn(ChargerBot, spacing=45),
Spawn(ChargerBot, spacing=45)
if player_count > 1
else None,
(
Spawn(ChargerBot, spacing=45)
if player_count > 1
else None
),
Spawn(BrawlerBot, spacing=6),
Spawn(BrawlerBot, spacing=6)
if self._preset is Preset.PRO
else None,
Spawn(BrawlerBot, spacing=6)
if player_count > 2
else None,
(
Spawn(BrawlerBot, spacing=6)
if self._preset is Preset.PRO
else None
),
(
Spawn(BrawlerBot, spacing=6)
if player_count > 2
else None
),
],
),
Wave(
@ -492,15 +541,21 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]):
Spawn(ChargerBot, spacing=30),
Spawn(TriggerBot, spacing=30),
Spawn(TriggerBot, spacing=30),
Spawn(TriggerBot, spacing=30)
if self._preset is Preset.PRO
else None,
Spawn(TriggerBot, spacing=30)
if player_count > 1
else None,
Spawn(TriggerBot, spacing=30)
if player_count > 3
else None,
(
Spawn(TriggerBot, spacing=30)
if self._preset is Preset.PRO
else None
),
(
Spawn(TriggerBot, spacing=30)
if player_count > 1
else None
),
(
Spawn(TriggerBot, spacing=30)
if player_count > 3
else None
),
Spawn(ChargerBot, spacing=30),
],
),
@ -508,16 +563,22 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]):
base_angle=90,
entries=[
Spawn(StickyBot, spacing=50),
Spawn(StickyBot, spacing=50)
if self._preset is Preset.PRO
else None,
(
Spawn(StickyBot, spacing=50)
if self._preset is Preset.PRO
else None
),
Spawn(StickyBot, spacing=50),
Spawn(StickyBot, spacing=50)
if player_count > 1
else None,
Spawn(StickyBot, spacing=50)
if player_count > 3
else None,
(
Spawn(StickyBot, spacing=50)
if player_count > 1
else None
),
(
Spawn(StickyBot, spacing=50)
if player_count > 3
else None
),
],
),
Wave(
@ -525,14 +586,18 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]):
entries=[
Spawn(TriggerBot, spacing=72),
Spawn(TriggerBot, spacing=72),
Spawn(TriggerBot, spacing=72)
if self._preset is Preset.PRO
else None,
(
Spawn(TriggerBot, spacing=72)
if self._preset is Preset.PRO
else None
),
Spawn(TriggerBot, spacing=72),
Spawn(TriggerBot, spacing=72),
Spawn(TriggerBot, spacing=36)
if player_count > 2
else None,
(
Spawn(TriggerBot, spacing=36)
if player_count > 2
else None
),
],
),
Wave(
@ -540,15 +605,21 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]):
entries=[
Spawn(ChargerBotProShielded, spacing=50),
Spawn(ChargerBotProShielded, spacing=50),
Spawn(ChargerBotProShielded, spacing=50)
if self._preset is Preset.PRO
else None,
Spawn(ChargerBotProShielded, spacing=50)
if player_count > 1
else None,
Spawn(ChargerBotProShielded, spacing=50)
if player_count > 2
else None,
(
Spawn(ChargerBotProShielded, spacing=50)
if self._preset is Preset.PRO
else None
),
(
Spawn(ChargerBotProShielded, spacing=50)
if player_count > 1
else None
),
(
Spawn(ChargerBotProShielded, spacing=50)
if player_count > 2
else None
),
],
),
]
@ -566,15 +637,21 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]):
self._waves = [
Wave(
entries=[
Spawn(BomberBotProStatic, Point.TURRET_TOP_MIDDLE_LEFT)
if hard
else None,
(
Spawn(
BomberBotProStatic, Point.TURRET_TOP_MIDDLE_LEFT
)
if hard
else None
),
Spawn(
BomberBotProStatic, Point.TURRET_TOP_MIDDLE_RIGHT
),
Spawn(BomberBotProStatic, Point.TURRET_TOP_LEFT)
if player_count > 2
else None,
(
Spawn(BomberBotProStatic, Point.TURRET_TOP_LEFT)
if player_count > 2
else None
),
Spawn(ExplodeyBot, Point.TOP_RIGHT),
Delay(4.0),
Spawn(ExplodeyBot, Point.TOP_LEFT),
@ -584,9 +661,11 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]):
entries=[
Spawn(ChargerBot, Point.LEFT),
Spawn(ChargerBot, Point.RIGHT),
Spawn(ChargerBot, Point.RIGHT_UPPER_MORE)
if player_count > 2
else None,
(
Spawn(ChargerBot, Point.RIGHT_UPPER_MORE)
if player_count > 2
else None
),
Spawn(BomberBotProStatic, Point.TURRET_TOP_LEFT),
Spawn(BomberBotProStatic, Point.TURRET_TOP_RIGHT),
]
@ -594,29 +673,39 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]):
Wave(
entries=[
Spawn(TriggerBotPro, Point.TOP_RIGHT),
Spawn(TriggerBotPro, Point.RIGHT_UPPER_MORE)
if player_count > 1
else None,
(
Spawn(TriggerBotPro, Point.RIGHT_UPPER_MORE)
if player_count > 1
else None
),
Spawn(TriggerBotPro, Point.RIGHT_UPPER),
Spawn(TriggerBotPro, Point.RIGHT_LOWER)
if hard
else None,
Spawn(TriggerBotPro, Point.RIGHT_LOWER_MORE)
if player_count > 2
else None,
(
Spawn(TriggerBotPro, Point.RIGHT_LOWER)
if hard
else None
),
(
Spawn(TriggerBotPro, Point.RIGHT_LOWER_MORE)
if player_count > 2
else None
),
Spawn(TriggerBotPro, Point.BOTTOM_RIGHT),
]
),
Wave(
entries=[
Spawn(ChargerBotProShielded, Point.BOTTOM_RIGHT),
Spawn(ChargerBotProShielded, Point.BOTTOM)
if player_count > 2
else None,
(
Spawn(ChargerBotProShielded, Point.BOTTOM)
if player_count > 2
else None
),
Spawn(ChargerBotProShielded, Point.BOTTOM_LEFT),
Spawn(ChargerBotProShielded, Point.TOP)
if hard
else None,
(
Spawn(ChargerBotProShielded, Point.TOP)
if hard
else None
),
Spawn(BomberBotProStatic, Point.TURRET_TOP_MIDDLE),
]
),
@ -643,12 +732,21 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]):
Spawn(BomberBotProStatic, Point.TURRET_TOP_RIGHT),
Spawn(BomberBotProStatic, Point.TURRET_BOTTOM_LEFT),
Spawn(BomberBotProStatic, Point.TURRET_BOTTOM_RIGHT),
Spawn(BomberBotProStatic, Point.TURRET_TOP_MIDDLE_LEFT)
if hard
else None,
Spawn(BomberBotProStatic, Point.TURRET_TOP_MIDDLE_RIGHT)
if hard
else None,
(
Spawn(
BomberBotProStatic, Point.TURRET_TOP_MIDDLE_LEFT
)
if hard
else None
),
(
Spawn(
BomberBotProStatic,
Point.TURRET_TOP_MIDDLE_RIGHT,
)
if hard
else None
),
]
),
]
@ -667,12 +765,14 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]):
# Spit out a few powerups and start dropping more shortly.
self._drop_powerups(
standard_points=True,
poweruptype='curse'
if self._preset in [Preset.UBER, Preset.UBER_EASY]
else (
'land_mines'
if self._preset in [Preset.ROOKIE, Preset.ROOKIE_EASY]
else None
poweruptype=(
'curse'
if self._preset in [Preset.UBER, Preset.UBER_EASY]
else (
'land_mines'
if self._preset in [Preset.ROOKIE, Preset.ROOKIE_EASY]
else None
)
),
)
bs.timer(4.0, self._start_powerup_drops)

View file

@ -9,10 +9,9 @@ from __future__ import annotations
import random
import logging
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from dataclasses import dataclass
from typing_extensions import override
import bascenev1 as bs
from bascenev1lib.actor.bomb import Bomb
@ -778,9 +777,11 @@ class RaceGame(bs.TeamGameActivity[Player, Team]):
assert self._timer is not None
if self._timer.has_started():
self._timer.stop(
endtime=None
if self._last_team_time is None
else (self._timer.getstarttime() + self._last_team_time)
endtime=(
None
if self._last_team_time is None
else (self._timer.getstarttime() + self._last_team_time)
)
)
results = bs.GameResults()

View file

@ -14,9 +14,8 @@ import random
import logging
from enum import Enum
from dataclasses import dataclass
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, cast, Sequence, override
from typing_extensions import override
import bascenev1 as bs
from bascenev1lib.actor.popuptext import PopupText
@ -45,7 +44,7 @@ from bascenev1lib.actor.spazbot import (
)
if TYPE_CHECKING:
from typing import Any, Sequence
from typing import Any
class Preset(Enum):
@ -190,6 +189,7 @@ class RunaroundGame(bs.CoopGameActivity[Player, Team]):
self._lives_text: bs.NodeActor | None = None
self._flawless = True
self._time_bonus_timer: bs.Timer | None = None
self._lives_hbtime: bs.Timer | None = None
self._time_bonus_text: bs.NodeActor | None = None
self._time_bonus_mult: float | None = None
self._wave_text: bs.NodeActor | None = None
@ -279,9 +279,11 @@ class RunaroundGame(bs.CoopGameActivity[Player, Team]):
Spacing(duration=1.0),
Spawn(TriggerBot, path=3),
Spacing(duration=1.0),
Spawn(TriggerBot, path=1)
if (player_count > 1 and hard)
else None,
(
Spawn(TriggerBot, path=1)
if (player_count > 1 and hard)
else None
),
Spacing(duration=1.0),
Spawn(TriggerBot, path=2) if player_count > 2 else None,
Spacing(duration=1.0),
@ -320,17 +322,23 @@ class RunaroundGame(bs.CoopGameActivity[Player, Team]):
Spacing(duration=1.5),
Spawn(BomberBotProShielded, path=1) if hard else None,
Spacing(duration=1.5) if hard else None,
Spawn(BomberBotProShielded, path=3)
if player_count > 1
else None,
(
Spawn(BomberBotProShielded, path=3)
if player_count > 1
else None
),
Spacing(duration=1.5),
Spawn(BomberBotProShielded, path=2)
if player_count > 2
else None,
(
Spawn(BomberBotProShielded, path=2)
if player_count > 2
else None
),
Spacing(duration=1.5),
Spawn(BomberBotProShielded, path=1)
if player_count > 3
else None,
(
Spawn(BomberBotProShielded, path=1)
if player_count > 3
else None
),
]
),
]
@ -352,9 +360,11 @@ class RunaroundGame(bs.CoopGameActivity[Player, Team]):
BrawlerBotPro if hard else BrawlerBot,
point=Point.BOTTOM_LEFT,
),
Spawn(BrawlerBotPro, point=Point.BOTTOM_RIGHT)
if player_count > 2
else None,
(
Spawn(BrawlerBotPro, point=Point.BOTTOM_RIGHT)
if player_count > 2
else None
),
]
),
Wave(
@ -375,9 +385,11 @@ class RunaroundGame(bs.CoopGameActivity[Player, Team]):
Spawn(BomberBotProShielded, path=3),
Spawn(BomberBotProShielded, path=3),
Spawn(ChargerBot, point=Point.BOTTOM_RIGHT),
Spawn(ChargerBot, point=Point.BOTTOM_LEFT)
if player_count > 2
else None,
(
Spawn(ChargerBot, point=Point.BOTTOM_LEFT)
if player_count > 2
else None
),
]
),
Wave(
@ -388,12 +400,16 @@ class RunaroundGame(bs.CoopGameActivity[Player, Team]):
Spawn(TriggerBotPro, path=1 if hard else 2),
Spawn(TriggerBotPro, path=1 if hard else 2),
Spawn(TriggerBotPro, path=1 if hard else 2),
Spawn(TriggerBotPro, path=1 if hard else 2)
if player_count > 1
else None,
Spawn(TriggerBotPro, path=1 if hard else 2)
if player_count > 3
else None,
(
Spawn(TriggerBotPro, path=1 if hard else 2)
if player_count > 1
else None
),
(
Spawn(TriggerBotPro, path=1 if hard else 2)
if player_count > 3
else None
),
]
),
Wave(
@ -402,12 +418,20 @@ class RunaroundGame(bs.CoopGameActivity[Player, Team]):
TriggerBotProShielded if hard else TriggerBotPro,
point=Point.BOTTOM_LEFT,
),
Spawn(TriggerBotProShielded, point=Point.BOTTOM_RIGHT)
if hard
else None,
Spawn(TriggerBotProShielded, point=Point.BOTTOM_RIGHT)
if player_count > 2
else None,
(
Spawn(
TriggerBotProShielded, point=Point.BOTTOM_RIGHT
)
if hard
else None
),
(
Spawn(
TriggerBotProShielded, point=Point.BOTTOM_RIGHT
)
if player_count > 2
else None
),
Spawn(BomberBot, path=3),
Spawn(BomberBot, path=3),
Spacing(duration=5.0),
@ -425,15 +449,19 @@ class RunaroundGame(bs.CoopGameActivity[Player, Team]):
Spawn(StickyBot, point=Point.BOTTOM_RIGHT),
Spawn(BomberBotProShielded, path=2),
Spawn(BomberBotProShielded, path=2),
Spawn(StickyBot, point=Point.BOTTOM_RIGHT)
if player_count > 2
else None,
(
Spawn(StickyBot, point=Point.BOTTOM_RIGHT)
if player_count > 2
else None
),
Spawn(BomberBotProShielded, path=2),
Spawn(ExplodeyBot, point=Point.BOTTOM_LEFT),
Spawn(BomberBotProShielded, path=2),
Spawn(BomberBotProShielded, path=2)
if player_count > 1
else None,
(
Spawn(BomberBotProShielded, path=2)
if player_count > 1
else None
),
Spacing(duration=5.0),
Spawn(StickyBot, point=Point.BOTTOM_LEFT),
Spacing(duration=2.0),
@ -461,9 +489,7 @@ class RunaroundGame(bs.CoopGameActivity[Player, Team]):
l_offs = (
-80
if uiscale is bs.UIScale.SMALL
else -40
if uiscale is bs.UIScale.MEDIUM
else 0
else -40 if uiscale is bs.UIScale.MEDIUM else 0
)
self._lives_bg = bs.NodeActor(
@ -525,6 +551,18 @@ class RunaroundGame(bs.CoopGameActivity[Player, Team]):
if self._lives == 0:
self._bots.stop_moving()
self.continue_or_end_game()
# Heartbeat behavior
if self._lives < 5:
hbtime = 0.39 + (0.21 * self._lives)
self._lives_hbtime = bs.Timer(
hbtime, lambda: self.heart_dyin(True, hbtime), repeat=True
)
self.heart_dyin(True)
else:
self._lives_hbtime = None
self.heart_dyin(False)
assert self._lives_text is not None
assert self._lives_text.node
self._lives_text.node.text = str(self._lives)
@ -1366,3 +1404,43 @@ class RunaroundGame(bs.CoopGameActivity[Player, Team]):
def _set_can_end_wave(self) -> None:
self._can_end_wave = True
def heart_dyin(self, status: bool, time: float = 1.22) -> None:
"""Makes the UI heart beat at low health."""
assert self._lives_bg is not None
if self._lives_bg.node.exists():
return
heart = self._lives_bg.node
# Make the heart beat intensely!
if status:
bs.animate_array(
heart,
'scale',
2,
{
0: (90, 90),
time * 0.1: (105, 105),
time * 0.21: (88, 88),
time * 0.42: (90, 90),
time * 0.52: (105, 105),
time * 0.63: (88, 88),
time: (90, 90),
},
)
# Neutralize heartbeat (Done did when dead.)
else:
# Ew; janky old scenev1 has a single 'Node' Python type so
# it thinks heart.scale could be a few different things
# (float, Sequence[float], etc.). So we have to force the
# issue with a cast(). This should go away with scenev2/etc.
bs.animate_array(
heart,
'scale',
2,
{
0.0: cast(Sequence[float], heart.scale),
time: (90, 90),
},
)

View file

@ -8,9 +8,8 @@
from __future__ import annotations
import random
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import bascenev1 as bs
from bascenev1lib.actor.scoreboard import Scoreboard
@ -321,11 +320,15 @@ class Target(bs.Actor):
bs.getsound(
'orchestraHit4'
if streak > 3
else 'orchestraHit3'
if streak > 2
else 'orchestraHit2'
if streak > 1
else 'orchestraHit'
else (
'orchestraHit3'
if streak > 2
else (
'orchestraHit2'
if streak > 1
else 'orchestraHit'
)
)
).play()
elif dist <= self._r2 + self._rfudge:
self._nodes[0].color = cdull

View file

@ -7,9 +7,8 @@ from __future__ import annotations
import random
import logging
from dataclasses import dataclass
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import bascenev1 as bs
from bascenev1lib.actor.playerspaz import PlayerSpaz

View file

@ -8,9 +8,8 @@ from __future__ import annotations
import time
import random
import weakref
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import bascenev1 as bs
import bauiv1 as bui
@ -134,8 +133,8 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
text = bs.Lstr(
value='${V} (${B}) (${D})',
subs=[
('${V}', app.env.version),
('${B}', str(app.env.build_number)),
('${V}', app.env.engine_version),
('${B}', str(app.env.engine_build_number)),
('${D}', bs.Lstr(resource='debugText')),
],
)
@ -143,12 +142,14 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
text = bs.Lstr(
value='${V} (${B})',
subs=[
('${V}', app.env.version),
('${B}', str(app.env.build_number)),
('${V}', app.env.engine_version),
('${B}', str(app.env.engine_build_number)),
],
)
else:
text = bs.Lstr(value='${V}', subs=[('${V}', app.env.version)])
text = bs.Lstr(
value='${V}', subs=[('${V}', app.env.engine_version)]
)
scale = 0.9 if (uiscale is bs.UIScale.SMALL or vr_mode) else 0.7
color = (1, 1, 1, 1) if vr_mode else (0.5, 0.6, 0.5, 0.7)
self.version = bs.NodeActor(

View file

@ -5,9 +5,8 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import bascenev1 as bs
from bascenev1lib.gameutils import SharedObjects

View file

@ -17,9 +17,8 @@ from __future__ import annotations
import math
import logging
from collections import deque
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import bascenev1 as bs
from bascenev1lib.actor.spaz import Spaz

View file

@ -70,6 +70,11 @@ from babase import (
native_review_request_supported,
NotFoundError,
open_file_externally,
open_url,
overlay_web_browser_close,
overlay_web_browser_is_open,
overlay_web_browser_is_supported,
overlay_web_browser_open_url,
Permission,
Plugin,
PluginSpec,
@ -106,7 +111,6 @@ from _bauiv1 import (
imagewidget,
is_party_icon_visible,
Mesh,
open_url,
rowwidget,
scrollwidget,
set_party_icon_always_visible,
@ -191,6 +195,10 @@ __all__ = [
'NotFoundError',
'open_file_externally',
'open_url',
'overlay_web_browser_close',
'overlay_web_browser_is_open',
'overlay_web_browser_is_supported',
'overlay_web_browser_open_url',
'Permission',
'Plugin',
'PluginSpec',

View file

@ -6,9 +6,8 @@ from __future__ import annotations
import logging
import inspect
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import babase
import _bauiv1
@ -43,7 +42,10 @@ class UIV1Subsystem(babase.AppSubsystem):
self._uiscale: babase.UIScale
interfacetype = env['ui_scale']
interfacetype = babase.app.config.get('UI Scale', env['ui_scale'])
if interfacetype == 'auto':
interfacetype = env['ui_scale']
if interfacetype == 'large':
self._uiscale = babase.UIScale.LARGE
elif interfacetype == 'medium':

View file

@ -7,9 +7,8 @@ from __future__ import annotations
import os
import weakref
from dataclasses import dataclass
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, override
from typing_extensions import override
import babase
import _bauiv1
@ -164,9 +163,7 @@ class UIController:
entrynew = (
self._dialog_stack[-1]
if self._dialog_stack
else self._main_stack[-1]
if self._main_stack
else None
else self._main_stack[-1] if self._main_stack else None
)
if entrynew is not None:
entrynew.create()

Some files were not shown because too many files have changed in this diff Show more