diff --git a/.efrocachemap b/.efrocachemap index 8b7ec793..26963766 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -4132,16 +4132,16 @@ "assets/build/windows/x64/python.exe": "https://files.ballistica.net/cache/ba1/25/a7/dc87c1be41605eb6fefd0145144c", "assets/build/windows/x64/python37.dll": "https://files.ballistica.net/cache/ba1/b9/e4/d912f56e42e9991bcbb4c804cfcb", "assets/build/windows/x64/pythonw.exe": "https://files.ballistica.net/cache/ba1/6c/bb/b6f52c306aa4e88061510e96cefe", - "build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/e4/f0/c3261a8a7391a2b654da82c35c21", - "build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/33/e2/85a8cbf23404612e6761cf02348b", - "build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/5b/0d/d906be5fc2a75b5214a94cafe6ca", - "build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/6b/53/72d665caf5dfed84312ae6862642", - "build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/d9/68/2d98f2d1ee67b6d54adbdf76c863", - "build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/e5/3a/33f8f311260542126acea44d05e2", - "build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/18/eb/dd39bcfa33983864089d3fb7f666", - "build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/f8/1d/3440010fcbcf18c5a6b2966f7fca", - "build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/9b/fa/b4ad4b8b82fefe1faad610065b9f", - "build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/7f/b9/03af92db6140135ddfa467a1545d", - "build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/69/b9/687fc5fa38c3faf7a47eb4241a3c", - "build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/cf/2b/589db924b489972981e10850a4e3" + "build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/a1/97/fd0e5553917019236912a7010d38", + "build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/43/32/c48a4da7fcc8c17132077da852d7", + "build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/75/13/2bc3ae9026386d1b515ede107e17", + "build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/1f/9a/b112f788f528ec9e3627622698ac", + "build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/a1/87/fdb3e925f3be1cc353db94d61c5a", + "build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/fa/ba/125457c69bc2940850ab84c4cc6a", + "build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/a1/d4/5efcef1f85b2bfea7bbb81800580", + "build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/fa/66/75a3a0f27d07131473e804a5b937", + "build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/51/6c/911ba80ba913e6dac1cafa5c8a74", + "build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/01/cc/cf896173a8d5731ada2aace59146", + "build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/a2/12/aa1f0ca5506f0b57f93ca9adf7be", + "build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/e1/5f/f372d0ba9591b99284b9b50d7cdf" } \ No newline at end of file diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml index 898fe13e..1cf96c30 100644 --- a/.idea/dictionaries/ericf.xml +++ b/.idea/dictionaries/ericf.xml @@ -480,6 +480,7 @@ downmix dpad dpath + dprofilename drawscore drawscreen dripity @@ -756,8 +757,10 @@ getcurrency getcwd getdata + getinputdevice getkillerplayer getlevelname + getlog getmaps getmodel getnodes @@ -937,6 +940,7 @@ joedeshon johab joinable + joinmsg jovica jsonstr jsonstrbase @@ -1267,6 +1271,8 @@ openssh operasinger oppnode + opposingbody + opposingnode opstr optparse orchestrahitsound @@ -1504,6 +1510,7 @@ qrencode qual quoprimime + rando randommodule randval rankbutton @@ -1672,6 +1679,7 @@ soundtrackname sourceimages sourcelines + sourcenode spacelen spacingstr spammers diff --git a/assets/src/ba_data/python/_ba.py b/assets/src/ba_data/python/_ba.py index 6b593af3..f1f90179 100644 --- a/assets/src/ba_data/python/_ba.py +++ b/assets/src/ba_data/python/_ba.py @@ -34,7 +34,7 @@ NOTE: This file was autogenerated by gendummymodule; do not edit by hand. """ # (hash we can use to see if this file is out of date) -# SOURCES_HASH=164420280597992494471294420110866243586 +# SOURCES_HASH=318889180473540875493180206824864665481 # I'm sorry Pylint. I know this file saddens you. Be strong. # pylint: disable=useless-suppression @@ -691,8 +691,8 @@ class Node: def delete(self, ignore_missing: bool = True) -> None: """delete(ignore_missing: bool = True) -> None - Delete the node. Ignores already-deleted nodes unless ignore_missing - is False, in which case an Exception is thrown. + Delete the node. Ignores already-deleted nodes if ignore_missing + is True; otherwise a ba.NodeNotFoundError is thrown. """ return None @@ -716,6 +716,7 @@ class Node: """ return str() + # Show that ur return type varies based on "doraise" value: @overload def getdelegate(self, type: Type[_T], @@ -733,7 +734,7 @@ class Node: If the node has no delegate or it is not an instance of the passed type, then None will be returned. If 'doraise' is True, then an - Exception will be raised instead in such cases. + ba.DelegateNotFoundError will be raised instead. """ return None @@ -818,6 +819,9 @@ class SessionPlayer: storing data associated with this Player. This gets cleared for each new ba.Activity. + inputdevice: ba.InputDevice + The input device associated with the player. + color: Sequence[float] The base color for this Player. In team games this will match the ba.SessionTeam's color. @@ -839,6 +843,7 @@ class SessionPlayer: team: ba.SessionTeam sessiondata: Dict gamedata: Dict + inputdevice: ba.InputDevice color: Sequence[float] highlight: Sequence[float] character: str @@ -892,14 +897,6 @@ class SessionPlayer: """ return {'foo': 'bar'} - def get_input_device(self) -> ba.InputDevice: - """get_input_device() -> ba.InputDevice - - Returns the player's input device. - """ - import ba # pylint: disable=cyclic-import - return ba.InputDevice() - def get_name(self, full: bool = False, icon: bool = True) -> str: """get_name(full: bool = False, icon: bool = True) -> str @@ -2065,22 +2062,6 @@ def get_idle_time() -> int: return int() -def get_input_device(name: str, - unique_id: str, - doraise: bool = True) -> ba.InputDevice: - """get_input_device(name: str, unique_id: str, doraise: bool = True) - -> ba.InputDevice - - (internal) - - Given a type name and a unique identifier, returns an InputDevice. - Throws an Exception if the input-device is not found, or returns None - if 'doraise' is False. - """ - import ba # pylint: disable=cyclic-import - return ba.InputDevice() - - def get_local_active_input_devices_count() -> int: """get_local_active_input_devices_count() -> int @@ -2089,14 +2070,6 @@ def get_local_active_input_devices_count() -> int: return int() -def get_log() -> str: - """get_log() -> str - - (internal) - """ - return str() - - def get_log_file_path() -> str: """get_log_file_path() -> str @@ -2389,6 +2362,41 @@ def getdata(name: str) -> ba.Data: return ba.Data() +# Show that our return type varies based on "doraise" value: +@overload +def getinputdevice(name: str, + unique_id: str, + doraise: Literal[True] = True) -> ba.InputDevice: + ... + + +@overload +def getinputdevice(name: str, unique_id: str, + doraise: Literal[False]) -> Optional[ba.InputDevice]: + ... + + +def getinputdevice(name: str, unique_id: str, doraise: bool = True) -> Any: + """getinputdevice(name: str, unique_id: str, doraise: bool = True) + -> + + (internal) + + Given a type name and a unique identifier, returns an InputDevice. + Throws an Exception if the input-device is not found, or returns None + if 'doraise' is False. + """ + return None + + +def getlog() -> str: + """getlog() -> str + + (internal) + """ + return str() + + def getmodel(name: str) -> ba.Model: """getmodel(name: str) -> ba.Model diff --git a/assets/src/ba_data/python/ba/__init__.py b/assets/src/ba_data/python/ba/__init__.py index 25869a72..9e808242 100644 --- a/assets/src/ba_data/python/ba/__init__.py +++ b/assets/src/ba_data/python/ba/__init__.py @@ -53,7 +53,7 @@ from ba._error import (print_exception, print_error, NotFoundError, InputDeviceNotFoundError, WidgetNotFoundError, ActivityNotFoundError, TeamNotFoundError, SessionTeamNotFoundError, SessionNotFoundError, - DependencyError) + DelegateNotFoundError, DependencyError) from ba._freeforallsession import FreeForAllSession from ba._gameactivity import GameActivity from ba._gameresults import TeamGameResults diff --git a/assets/src/ba_data/python/ba/_app.py b/assets/src/ba_data/python/ba/_app.py index 484e1e67..99129efc 100644 --- a/assets/src/ba_data/python/ba/_app.py +++ b/assets/src/ba_data/python/ba/_app.py @@ -22,6 +22,7 @@ from __future__ import annotations import time +import random from typing import TYPE_CHECKING import _ba @@ -376,7 +377,7 @@ class App: # Lobby. self.lobby_random_profile_index: int = 1 - self.lobby_random_char_index_offset: Optional[int] = None + self.lobby_random_char_index_offset = random.randrange(1000) self.lobby_account_profile_device_id: Optional[int] = None # Main Menu. @@ -722,7 +723,7 @@ class App: game: str, force: bool = False, args: Dict = None) -> bool: - """High level way to launch a co-op session locally.""" + """High level way to launch a local co-op session.""" # pylint: disable=cyclic-import from ba._campaign import get_campaign from bastd.ui.coop.level import CoopLevelLockedWindow @@ -788,7 +789,7 @@ class App: color=(1, 1, 0)), timetype=TimeType.REAL) - def shutdown(self) -> None: + def on_app_shutdown(self) -> None: """(internal)""" self.music.on_app_shutdown() diff --git a/assets/src/ba_data/python/ba/_apputils.py b/assets/src/ba_data/python/ba/_apputils.py index 25a77d7d..1c8b1da5 100644 --- a/assets/src/ba_data/python/ba/_apputils.py +++ b/assets/src/ba_data/python/ba/_apputils.py @@ -43,7 +43,7 @@ def is_browser_likely_available() -> bool: """ app = _ba.app platform = app.platform - touchscreen = _ba.get_input_device('TouchScreen', '#1', doraise=False) + touchscreen = _ba.getinputdevice('TouchScreen', '#1', doraise=False) # If we're on a vr device or an android device with no touchscreen, # assume no browser. @@ -100,7 +100,7 @@ def handle_log() -> None: except Exception: activityname = 'unavailable' info = { - 'log': _ba.get_log(), + 'log': _ba.getlog(), 'version': app.version, 'build': app.build_number, 'userAgentString': app.user_agent_string, diff --git a/assets/src/ba_data/python/ba/_collision.py b/assets/src/ba_data/python/ba/_collision.py index b41aa104..7d4261dd 100644 --- a/assets/src/ba_data/python/ba/_collision.py +++ b/assets/src/ba_data/python/ba/_collision.py @@ -40,37 +40,37 @@ class Collision: return _ba.Vec3(_ba.get_collision_info('position')) @property - def source_node(self) -> ba.Node: + def sourcenode(self) -> ba.Node: """The node containing the material triggering the current callback. Throws a ba.NodeNotFoundError if the node does not exist, though the node should always exist (at least at the start of the collision callback). """ - node = _ba.get_collision_info('source_node') + node = _ba.get_collision_info('sourcenode') assert isinstance(node, (_ba.Node, type(None))) if not node: raise NodeNotFoundError() return node @property - def opposing_node(self) -> ba.Node: + def opposingnode(self) -> ba.Node: """The node the current callback material node is hitting. Throws a ba.NodeNotFoundError if the node does not exist. This can be expected in some cases such as in 'disconnect' callbacks triggered by deleting a currently-colliding node. """ - node = _ba.get_collision_info('opposing_node') + node = _ba.get_collision_info('opposingnode') assert isinstance(node, (_ba.Node, type(None))) if not node: raise NodeNotFoundError() return node @property - def opposing_body(self) -> int: + def opposingbody(self) -> int: """The body index on the opposing node in the current collision.""" - body = _ba.get_collision_info('opposing_body') + body = _ba.get_collision_info('opposingbody') assert isinstance(body, int) return body diff --git a/assets/src/ba_data/python/ba/_error.py b/assets/src/ba_data/python/ba/_error.py index 42e8cd89..afb36569 100644 --- a/assets/src/ba_data/python/ba/_error.py +++ b/assets/src/ba_data/python/ba/_error.py @@ -77,6 +77,13 @@ class TeamNotFoundError(NotFoundError): """ +class DelegateNotFoundError(NotFoundError): + """Exception raised when an expected delegate object does not exist. + + category: Exception Classes + """ + + class SessionTeamNotFoundError(NotFoundError): """Exception raised when an expected ba.SessionTeam does not exist. diff --git a/assets/src/ba_data/python/ba/_gameutils.py b/assets/src/ba_data/python/ba/_gameutils.py index fcaf3f4b..e6cb6adf 100644 --- a/assets/src/ba_data/python/ba/_gameutils.py +++ b/assets/src/ba_data/python/ba/_gameutils.py @@ -194,10 +194,10 @@ def animate(node: ba.Node, # Temp sanity check while we transition from milliseconds to seconds # based time values. - if _ba.app.test_build and not suppress_format_warning: - for item in items: - # (PyCharm seems to think item is a float, not a tuple) - _ba.time_format_check(timeformat, item[0]) + if __debug__: + if not suppress_format_warning: + for item in items: + _ba.time_format_check(timeformat, item[0]) curve = _ba.newnode('animcurve', owner=node, @@ -221,7 +221,6 @@ def animate(node: ba.Node, # FIXME: Even if we are looping we should have a way to die once we # get disconnected. if not loop: - # (PyCharm seems to think item is a float, not a tuple) _ba.timer(int(mult * items[-1][0]) + 1000, curve.delete, timeformat=TimeFormat.MILLISECONDS) @@ -259,10 +258,11 @@ def animate_array(node: ba.Node, # Temp sanity check while we transition from milliseconds to seconds # based time values. - if _ba.app.test_build and not suppress_format_warning: - for item in items: - # (PyCharm seems to think item is a float, not a tuple) - _ba.time_format_check(timeformat, item[0]) + if __debug__: + if not suppress_format_warning: + for item in items: + # (PyCharm seems to think item is a float, not a tuple) + _ba.time_format_check(timeformat, item[0]) if timeformat is TimeFormat.SECONDS: mult = 1000 @@ -381,8 +381,9 @@ def timestring(timeval: float, # Temp sanity check while we transition from milliseconds to seconds # based time values. - if _ba.app.test_build and not suppress_format_warning: - _ba.time_format_check(timeformat, timeval) + if __debug__: + if not suppress_format_warning: + _ba.time_format_check(timeformat, timeval) # We operate on milliseconds internally. if timeformat is TimeFormat.SECONDS: diff --git a/assets/src/ba_data/python/ba/_hooks.py b/assets/src/ba_data/python/ba/_hooks.py index 7c1d2c3c..c12b3c25 100644 --- a/assets/src/ba_data/python/ba/_hooks.py +++ b/assets/src/ba_data/python/ba/_hooks.py @@ -293,7 +293,7 @@ def do_quit() -> None: def shutdown() -> None: - _ba.app.shutdown() + _ba.app.on_app_shutdown() def gc_disable() -> None: diff --git a/assets/src/ba_data/python/ba/_lobby.py b/assets/src/ba_data/python/ba/_lobby.py index 30a08245..8d4716fe 100644 --- a/assets/src/ba_data/python/ba/_lobby.py +++ b/assets/src/ba_data/python/ba/_lobby.py @@ -19,16 +19,19 @@ # SOFTWARE. # ----------------------------------------------------------------------------- """Implements lobby system for gathering before games, char select, etc.""" -# pylint: disable=too-many-lines from __future__ import annotations -import random import weakref from dataclasses import dataclass from typing import TYPE_CHECKING import _ba +from ba._error import print_exception, print_error, NotFoundError +from ba._gameutils import animate, animate_array +from ba._lang import Lstr +from ba._enums import SpecialChar +from ba._profile import get_player_profile_colors if TYPE_CHECKING: from typing import Optional, List, Dict, Any, Sequence, Union @@ -44,45 +47,22 @@ class JoinInfo: """Display useful info for joiners.""" def __init__(self, lobby: ba.Lobby): - # pylint: disable=too-many-locals - from ba import _input - from ba._lang import Lstr from ba._nodeactor import NodeActor from ba._general import WeakCall - from ba._enums import SpecialChar - can_switch_teams = (len(lobby.teams) > 1) self._state = 0 - press_to_punch: Union[str, - ba.Lstr] = _ba.charstr(SpecialChar.LEFT_BUTTON) - press_to_bomb: Union[str, - ba.Lstr] = _ba.charstr(SpecialChar.RIGHT_BUTTON) + self._press_to_punch: Union[str, ba.Lstr] = _ba.charstr( + SpecialChar.LEFT_BUTTON) + self._press_to_bomb: Union[str, ba.Lstr] = _ba.charstr( + SpecialChar.RIGHT_BUTTON) + self._joinmsg = Lstr(resource='pressAnyButtonToJoinText') + can_switch_teams = (len(lobby.teams) > 1) # If we have a keyboard, grab keys for punch and pickup. # FIXME: This of course is only correct on the local device; # Should change this for net games. - keyboard: Optional[ba.InputDevice] = _ba.get_input_device( - 'Keyboard', '#1', doraise=False) + keyboard = _ba.getinputdevice('Keyboard', '#1', doraise=False) if keyboard is not None: - punch_key = keyboard.get_button_name( - _input.get_device_value(keyboard, 'buttonPunch')) - press_to_punch = Lstr(resource='orText', - subs=[('${A}', - Lstr(value='\'${K}\'', - subs=[('${K}', punch_key)])), - ('${B}', press_to_punch)]) - bomb_key = keyboard.get_button_name( - _input.get_device_value(keyboard, 'buttonBomb')) - press_to_bomb = Lstr(resource='orText', - subs=[('${A}', - Lstr(value='\'${K}\'', - subs=[('${K}', bomb_key)])), - ('${B}', press_to_bomb)]) - join_str = Lstr(value='${A} < ${B} >', - subs=[('${A}', - Lstr(resource='pressPunchToJoinText')), - ('${B}', press_to_punch)]) - else: - join_str = Lstr(resource='pressAnyButtonToJoinText') + self._update_for_keyboard(keyboard) flatness = 1.0 if _ba.app.vr_mode else 0.0 self._text = NodeActor( @@ -94,11 +74,11 @@ class JoinInfo: 'h_align': 'center', 'color': (0.7, 0.7, 0.95, 1.0), 'flatness': flatness, - 'text': join_str + 'text': self._joinmsg })) if _ba.app.kiosk_mode: - self._messages = [join_str] + self._messages = [self._joinmsg] else: msg1 = Lstr(resource='pressToSelectProfileText', subs=[ @@ -108,15 +88,38 @@ class JoinInfo: msg2 = Lstr(resource='pressToOverrideCharacterText', subs=[('${BUTTONS}', Lstr(resource='bombBoldText'))]) msg3 = Lstr(value='${A} < ${B} >', - subs=[('${A}', msg2), ('${B}', press_to_bomb)]) + subs=[('${A}', msg2), ('${B}', self._press_to_bomb)]) self._messages = (([ - Lstr(resource='pressToSelectTeamText', - subs=[('${BUTTONS}', _ba.charstr(SpecialChar.LEFT_ARROW) + - ' ' + _ba.charstr(SpecialChar.RIGHT_ARROW))]) - ] if can_switch_teams else []) + [msg1] + [msg3] + [join_str]) + Lstr( + resource='pressToSelectTeamText', + subs=[('${BUTTONS}', _ba.charstr(SpecialChar.LEFT_ARROW) + + ' ' + _ba.charstr(SpecialChar.RIGHT_ARROW))], + ) + ] if can_switch_teams else []) + [msg1] + [msg3] + [self._joinmsg]) self._timer = _ba.Timer(4.0, WeakCall(self._update), repeat=True) + def _update_for_keyboard(self, keyboard: ba.InputDevice) -> None: + from ba import _input + punch_key = keyboard.get_button_name( + _input.get_device_value(keyboard, 'buttonPunch')) + self._press_to_punch = Lstr(resource='orText', + subs=[('${A}', + Lstr(value='\'${K}\'', + subs=[('${K}', punch_key)])), + ('${B}', self._press_to_punch)]) + bomb_key = keyboard.get_button_name( + _input.get_device_value(keyboard, 'buttonBomb')) + self._press_to_bomb = Lstr(resource='orText', + subs=[('${A}', + Lstr(value='\'${K}\'', + subs=[('${K}', bomb_key)])), + ('${B}', self._press_to_bomb)]) + self._joinmsg = Lstr(value='${A} < ${B} >', + subs=[('${A}', + Lstr(resource='pressPunchToJoinText')), + ('${B}', self._press_to_punch)]) + def _update(self) -> None: assert self._text.node self._text.node.text = self._messages[self._state] @@ -150,13 +153,6 @@ class Chooser: def __init__(self, vpos: float, player: _ba.SessionPlayer, lobby: 'Lobby') -> None: - # FIXME: Tidy up around here. - # pylint: disable=too-many-branches - # pylint: disable=too-many-statements - from ba import _gameutils - from ba import _profile - from ba import _lang - app = _ba.app self._deek_sound = _ba.getsound('deek') self._click_sound = _ba.getsound('click01') self._punchsound = _ba.getsound('punch01') @@ -172,11 +168,16 @@ class Chooser: self._profilename = '' self._profilenames: List[str] = [] self._ready: bool = False - self.character_names: List[str] = [] - self.last_change: Sequence[Union[float, int]] = (0, 0) + self._character_names: List[str] = [] + self._last_change: Sequence[Union[float, int]] = (0, 0) + self._profiles: Dict[str, Dict[str, Any]] = {} - # Hmm does this need to be public? - self.profiles: Dict[str, Dict[str, Any]] = {} + app = _ba.app + + # try: + # print(player.inputdevice) + # except Exception as exc: + # print('GOT EXC', type(exc)) # Load available profiles either from the local config or from the # remote device. @@ -186,109 +187,26 @@ class Chooser: # the team-id! self._selected_team_index: int = self.lobby.next_add_team - # Store a persistent random character index; we'll use this for the - # '_random' profile. Let's use their input_device id to seed it. This - # will give a persistent character for them between games and will - # distribute characters nicely if everyone is random. - try: - input_device_id = self._player.get_input_device().id - except Exception: - from ba import _error - _error.print_exception('Error getting device-id on chooser create') - input_device_id = 0 + # Store a persistent random character index and colors; we'll use this + # for the '_random' profile. Let's use their input_device id to seed + # it. This will give a persistent character for them between games + # and will distribute characters nicely if everyone is random. + self._random_color, self._random_highlight = ( + get_player_profile_colors(None)) - if app.lobby_random_char_index_offset is None: - - # We want the first device that asks for a chooser to always get - # spaz as a random character.. - # scratch that.. we now kinda accomplish the same thing with - # account profiles so lets just be fully random here. - app.lobby_random_char_index_offset = (random.randrange(1000)) - - # To calc our random index we pick a random character out of our + # To calc our random character we pick a random one out of our # unlocked list and then locate that character's index in the full # list. char_index_offset = app.lobby_random_char_index_offset - assert char_index_offset is not None - self._random_character_index = ((input_device_id + char_index_offset) % - len(self.character_names)) - self._random_color, self._random_highlight = ( - _profile.get_player_profile_colors(None)) + self._random_character_index = ( + (player.inputdevice.id + char_index_offset) % + len(self._character_names)) - # Attempt to pick an initial profile based on what's been stored - # for this input device. - input_device = self._player.get_input_device() - try: - name = input_device.name - unique_id = input_device.unique_identifier - self._profilename = ( - app.config['Default Player Profiles'][name + ' ' + unique_id]) - self._profileindex = self._profilenames.index(self._profilename) + # Attempt to set an initial profile based on what was used previously + # for this input-device, etc. + self._profileindex = self._select_initial_profile() + self._profilename = self._profilenames[self._profileindex] - # If this one is __account__ and is local and we haven't marked - # anyone as the account-profile device yet, mark this guy as it. - # (prevents the next joiner from getting the account profile too). - if (self._profilename == '__account__' - and not input_device.is_remote_client - and app.lobby_account_profile_device_id is None): - app.lobby_account_profile_device_id = input_device_id - - # Well hmm that didn't work.. pick __account__, _random, or some - # other random profile. - except Exception: - - profilenames = self._profilenames - - # We want the first local input-device in the game to latch on to - # the account profile. - if (not input_device.is_remote_client - and not input_device.is_controller_app): - if (app.lobby_account_profile_device_id is None - and '__account__' in profilenames): - app.lobby_account_profile_device_id = input_device_id - - # If this is the designated account-profile-device, try to default - # to the account profile. - if (input_device_id == app.lobby_account_profile_device_id - and '__account__' in profilenames): - self._profileindex = profilenames.index('__account__') - else: - - # If this is the controller app, it defaults to using a random - # profile (since we can pull the random name from the app). - if input_device.is_controller_app: - self._profileindex = profilenames.index('_random') - else: - - # If its a client connection, for now just force - # the account profile if possible.. (need to provide a - # way for clients to specify/remember their default - # profile on remote servers that do not already know them). - if (input_device.is_remote_client - and '__account__' in profilenames): - self._profileindex = profilenames.index('__account__') - else: - - # Cycle through our non-random profiles once; after - # that, everyone gets random. - while (app.lobby_random_profile_index < - len(profilenames) - and profilenames[app.lobby_random_profile_index] - in ('_random', '__account__', '_edit')): - app.lobby_random_profile_index += 1 - if (app.lobby_random_profile_index < - len(profilenames)): - self._profileindex = ( - app.lobby_random_profile_index) - app.lobby_random_profile_index += 1 - else: - self._profileindex = profilenames.index('_random') - - self._profilename = profilenames[self._profileindex] - - self.character_index = self._random_character_index - self._color = self._random_color - self._highlight = self._random_highlight self._text_node = _ba.newnode('text', delegate=self, attrs={ @@ -300,8 +218,7 @@ class Chooser: 'v_align': 'center', 'v_attach': 'top' }) - - _gameutils.animate(self._text_node, 'scale', {0: 0, 0.1: 1.0}) + animate(self._text_node, 'scale', {0: 0, 0.1: 1.0}) self.icon = _ba.newnode('image', owner=self._text_node, attrs={ @@ -311,20 +228,83 @@ class Chooser: 'attach': 'topCenter' }) - _gameutils.animate_array(self.icon, 'scale', 2, { - 0: (0, 0), - 0.1: (45, 45) - }) + animate_array(self.icon, 'scale', 2, {0: (0, 0), 0.1: (45, 45)}) + + # Set our initial name to '' in case anyone asks. + self._player.set_name(Lstr(resource='choosingPlayerText').evaluate(), + real=False) + + # Init these to our rando but they should get switched to the + # selected profile (if any) right after. + self._character_index = self._random_character_index + self._color = self._random_color + self._highlight = self._random_highlight + + self.update_from_profile() + self.update_position() + self._inited = True self._set_ready(False) - # Set our initial name to '' in case anyone asks. - self._player.set_name( - _lang.Lstr(resource='choosingPlayerText').evaluate(), real=False) + def _select_initial_profile(self) -> int: + app = _ba.app + profilenames = self._profilenames + inputdevice = self._player.inputdevice - self.update_from_player_profiles() - self.update_position() - self._inited = True + # If we've got a set profile name for this device, work backwards + # from that to get our index. + dprofilename = (app.config.get('Default Player Profiles', + {}).get(inputdevice.name + ' ' + + inputdevice.unique_identifier)) + if dprofilename is not None and dprofilename in profilenames: + # If we got '__account__' and its local and we haven't marked + # anyone as the 'account profile' device yet, mark this guy as + # it. (prevents the next joiner from getting the account + # profile too). + if (dprofilename == '__account__' + and not inputdevice.is_remote_client + and app.lobby_account_profile_device_id is None): + app.lobby_account_profile_device_id = inputdevice.id + return profilenames.index(dprofilename) + + # We want to mark the first local input-device in the game + # as the 'account profile' device. + if (not inputdevice.is_remote_client + and not inputdevice.is_controller_app): + if (app.lobby_account_profile_device_id is None + and '__account__' in profilenames): + app.lobby_account_profile_device_id = inputdevice.id + + # If this is the designated account-profile-device, try to default + # to the account profile. + if (inputdevice.id == app.lobby_account_profile_device_id + and '__account__' in profilenames): + return profilenames.index('__account__') + + # If this is the controller app, it defaults to using a random + # profile (since we can pull the random name from the app). + if inputdevice.is_controller_app and '_random' in profilenames: + return profilenames.index('_random') + + # If its a client connection, for now just force + # the account profile if possible.. (need to provide a + # way for clients to specify/remember their default + # profile on remote servers that do not already know them). + if inputdevice.is_remote_client and '__account__' in profilenames: + return profilenames.index('__account__') + + # Cycle through our non-random profiles once; after + # that, everyone gets random. + while (app.lobby_random_profile_index < len(profilenames) + and profilenames[app.lobby_random_profile_index] + in ('_random', '__account__', '_edit')): + app.lobby_random_profile_index += 1 + if app.lobby_random_profile_index < len(profilenames): + profileindex = app.lobby_random_profile_index + app.lobby_random_profile_index += 1 + return profileindex + assert '_random' in profilenames + return profilenames.index('_random') @property def player(self) -> ba.SessionPlayer: @@ -353,44 +333,39 @@ class Chooser: """The chooser's ba.Lobby.""" lobby = self._lobby() if lobby is None: - from ba import _error - raise _error.NotFoundError('Lobby does not exist.') + raise NotFoundError('Lobby does not exist.') return lobby def get_lobby(self) -> Optional[ba.Lobby]: """Return this chooser's lobby if it still exists; otherwise None.""" return self._lobby() - def update_from_player_profiles(self) -> None: - """Set character based on profile; otherwise use pre-picked random.""" - try: - from ba import _profile - - # Store the name even though we usually use index (in case - # the profile list changes) - self._profilename = self._profilenames[self._profileindex] - character = self.profiles[self._profilename]['character'] + def update_from_profile(self) -> None: + """Set character/colors based on the current profile.""" + self._profilename = self._profilenames[self._profileindex] + if self._profilename == '_edit': + pass + elif self._profilename == '_random': + self._character_index = self._random_character_index + self._color = self._random_color + self._highlight = self._random_highlight + else: + character = self._profiles[self._profilename]['character'] # At the moment we're not properly pulling the list # of available characters from clients, so profiles might use a # character not in their list. For now, just go ahead and add # a character name to their list as long as we're aware of it. # This just means they won't always be able to override their - # character to others they own, but profile characters should work - # (and we validate profiles on the master server so no exploit - # opportunities) - if (character not in self.character_names + # character to others they own, but profile characters + # should work (and we validate profiles on the master server + # so no exploit opportunities) + if (character not in self._character_names and character in _ba.app.spaz_appearances): - self.character_names.append(character) - self.character_index = self.character_names.index(character) - self._color, self._highlight = (_profile.get_player_profile_colors( - self._profilename, profiles=self.profiles)) - except Exception: - # FIXME: Should never use top level Exception for logic; only - # error catching (and they should always be logged). - self.character_index = self._random_character_index - self._color = self._random_color - self._highlight = self._random_highlight + self._character_names.append(character) + self._character_index = self._character_names.index(character) + self._color, self._highlight = (get_player_profile_colors( + self._profilename, profiles=self._profiles)) self._update_icon() self._update_text() @@ -401,52 +376,51 @@ class Chooser: # Re-construct our profile index and other stuff since the profile # list might have changed. - input_device = self._player.get_input_device() + input_device = self._player.inputdevice is_remote = input_device.is_remote_client is_test_input = input_device.name.startswith('TestInput') # Pull this player's list of unlocked characters. if is_remote: - # FIXME: Pull this from remote player (but make sure to - # filter it to ones we've got). - self.character_names = ['Spaz'] + # TODO: Pull this from the remote player. + # (but make sure to filter it to the ones we've got). + self._character_names = ['Spaz'] else: - self.character_names = self.lobby.character_names_local_unlocked + self._character_names = self.lobby.character_names_local_unlocked # If we're a local player, pull our local profiles from the config. # Otherwise ask the remote-input-device for its profile list. if is_remote: - self.profiles = input_device.get_player_profiles() + self._profiles = input_device.get_player_profiles() else: - self.profiles = app.config.get('Player Profiles', {}) + self._profiles = app.config.get('Player Profiles', {}) # These may have come over the wire from an older # (non-unicode/non-json) version. # Make sure they conform to our standards # (unicode strings, no tuples, etc) - self.profiles = json_prep(self.profiles) + self._profiles = json_prep(self._profiles) # Filter out any characters we're unaware of. - for profile in list(self.profiles.items()): + for profile in list(self._profiles.items()): if profile[1].get('character', '') not in app.spaz_appearances: profile[1]['character'] = 'Spaz' - # Add in a random one so we're ok even if there's no - # user-created profiles. - self.profiles['_random'] = {} + # Add in a random one so we're ok even if there's no user profiles. + self._profiles['_random'] = {} # In kiosk mode we disable account profiles to force random. if app.kiosk_mode: - if '__account__' in self.profiles: - del self.profiles['__account__'] + if '__account__' in self._profiles: + del self._profiles['__account__'] # For local devices, add it an 'edit' option which will pop up # the profile window. if not is_remote and not is_test_input and not app.kiosk_mode: - self.profiles['_edit'] = {} + self._profiles['_edit'] = {} # Build a sorted name list we can iterate through. - self._profilenames = list(self.profiles.keys()) + self._profilenames = list(self._profiles.keys()) self._profilenames.sort(key=lambda x: x.lower()) if self._profilename in self._profilenames: @@ -457,90 +431,66 @@ class Chooser: def update_position(self) -> None: """Update this chooser's position.""" - from ba import _gameutils - # Hmmm this shouldn't be happening. - if not self._text_node: - print('Error: chooser text nonexistent..') - import traceback - traceback.print_stack() - return + assert self._text_node spacing = 350 teams = self.lobby.teams offs = (spacing * -0.5 * len(teams) + spacing * self._selected_team_index + 250) if len(teams) > 1: offs -= 35 - _gameutils.animate_array(self._text_node, 'position', 2, { + animate_array(self._text_node, 'position', 2, { 0: self._text_node.position, 0.1: (-100 + offs, self._vpos + 23) }) - _gameutils.animate_array(self.icon, 'position', 2, { + animate_array(self.icon, 'position', 2, { 0: self.icon.position, 0.1: (-130 + offs, self._vpos + 22) }) def get_character_name(self) -> str: """Return the selected character name.""" - return self.character_names[self.character_index] + return self._character_names[self._character_index] def _do_nothing(self) -> None: """Does nothing! (hacky way to disable callbacks)""" def _get_name(self, full: bool = False) -> str: - # FIXME: Needs cleanup. - # pylint: disable=too-many-branches - from ba._lang import Lstr - from ba._enums import SpecialChar name_raw = name = self._profilenames[self._profileindex] clamp = False if name == '_random': - input_device: Optional[ba.InputDevice] try: - input_device = self._player.get_input_device() + name = (self._player.inputdevice.get_default_player_name()) except Exception: - input_device = None - if input_device is not None: - name = input_device.get_default_player_name() - else: + print_exception('Error getting _random chooser name.') name = 'Invalid' - if not full: - clamp = True + clamp = not full elif name == '__account__': try: - input_device = self._player.get_input_device() + name = self._player.inputdevice.get_account_name(full) except Exception: - input_device = None - if input_device is not None: - name = input_device.get_account_name(full) - else: + print_exception('Error getting account name for chooser.') name = 'Invalid' - if not full: - clamp = True + clamp = not full elif name == '_edit': - # FIXME: This causes problems as an Lstr, but its ok to - # explicitly translate for now since this is only shown on the - # host. (also should elaborate; don't remember what problems this - # caused) + # Explicitly flattening this to a str; it's only relevant on + # the host so that's ok. name = (Lstr( resource='createEditPlayerText', fallback_resource='editProfileWindow.titleNewText').evaluate()) else: - # If we have a regular profile marked as global with an icon, # use it (for full only). if full: try: - if self.profiles[name_raw].get('global', False): - icon = (self.profiles[name_raw]['icon'] - if 'icon' in self.profiles[name_raw] else + if self._profiles[name_raw].get('global', False): + icon = (self._profiles[name_raw]['icon'] + if 'icon' in self._profiles[name_raw] else _ba.charstr(SpecialChar.LOGO)) name = icon + name except Exception: - from ba import _error - _error.print_exception('Error applying global icon') + print_exception('Error applying global icon.') else: - # We now clamp non-full versions of names so there's at # least some hope of reading them in-game. clamp = True @@ -561,9 +511,9 @@ class Chooser: with _ba.Context('ui'): pbrowser.ProfileBrowserWindow(in_main_menu=False) - # give their input-device UI ownership too + # Give their input-device UI ownership too # (prevent someone else from snatching it in crowded games) - _ba.set_ui_input_device(self._player.get_input_device()) + _ba.set_ui_input_device(self._player.inputdevice) return if not ready: @@ -597,7 +547,7 @@ class Chooser: Call(self.handlemessage, ChangeMessage('ready', 0))) # Store the last profile picked by this input for reuse. - input_device = self._player.get_input_device() + input_device = self._player.inputdevice name = input_device.name unique_id = input_device.unique_identifier device_profiles = _ba.app.config.setdefault( @@ -607,7 +557,8 @@ class Chooser: # to random; in that case we'll want to start picking up custom # profiles if/when one is made so keep our setting cleared. special = ('_random', '_edit', '__account__') - have_custom_profiles = any(p not in special for p in self.profiles) + have_custom_profiles = any(p not in special + for p in self._profiles) profilekey = name + ' ' + unique_id if profilename == '_random' and not have_custom_profiles: @@ -643,7 +594,7 @@ class Chooser: # choosers that have been marked as ready. team_player_counts = {} for team in teams: - team_player_counts[team.id] = (len(team.players)) + team_player_counts[team.id] = len(team.players) for chooser in lobby.choosers: if chooser.ready: team_player_counts[chooser.get_team().id] += 1 @@ -668,15 +619,14 @@ class Chooser: # TODO: should handle this at the engine layer so this is unnecessary. def _handle_repeat_message_attack(self) -> None: now = _ba.time() - count = self.last_change[1] - if now - self.last_change[0] < QUICK_CHANGE_INTERVAL: + count = self._last_change[1] + if now - self._last_change[0] < QUICK_CHANGE_INTERVAL: count += 1 if count > MAX_QUICK_CHANGE_COUNT: - _ba.disconnect_client( - self._player.get_input_device().client_id) - elif now - self.last_change[0] > QUICK_CHANGE_RESET_INTERVAL: + _ba.disconnect_client(self._player.inputdevice.client_id) + elif now - self._last_change[0] > QUICK_CHANGE_RESET_INTERVAL: count = 0 - self.last_change = (now, count) + self._last_change = (now, count) def handlemessage(self, msg: Any) -> Any: """Standard generic message handler.""" @@ -686,13 +636,11 @@ class Chooser: # If we've been removed from the lobby, ignore this stuff. if self._dead: - from ba import _error - _error.print_error('chooser got ChangeMessage after dying') + print_error('chooser got ChangeMessage after dying') return if not self._text_node: - from ba import _error - _error.print_error('got ChangeMessage after nodes died') + print_error('got ChangeMessage after nodes died') return if msg.what == 'team': @@ -718,13 +666,13 @@ class Chooser: _ba.playsound(self._deek_sound) self._profileindex = ((self._profileindex + msg.value) % len(self._profilenames)) - self.update_from_player_profiles() + self.update_from_profile() elif msg.what == 'character': _ba.playsound(self._click_sound) # update our index in our local list of characters - self.character_index = ((self.character_index + msg.value) % - len(self.character_names)) + self._character_index = ((self._character_index + msg.value) % + len(self._character_names)) self._update_text() self._update_icon() @@ -732,8 +680,6 @@ class Chooser: self._handle_ready_msg(bool(msg.value)) def _update_text(self) -> None: - from ba import _gameutils - from ba._lang import Lstr assert self._text_node is not None if self._ready: @@ -751,7 +697,7 @@ class Chooser: # Flash as we're coming in. fin_color = _ba.safecolor(self.get_color()) + (1, ) if not self._inited: - _gameutils.animate_array(self._text_node, 'color', 4, { + animate_array(self._text_node, 'color', 4, { 0.15: fin_color, 0.25: (2, 2, 2, 1), 0.35: fin_color @@ -760,7 +706,7 @@ class Chooser: # Blend if we're in teams mode; switch instantly otherwise. if can_switch_teams: - _gameutils.animate_array(self._text_node, 'color', 4, { + animate_array(self._text_node, 'color', 4, { 0: self._text_node.color, 0.1: fin_color }) @@ -772,8 +718,6 @@ class Chooser: def get_color(self) -> Sequence[float]: """Return the currently selected color.""" val: Sequence[float] - # if self._profilenames[self._profileindex] == '_edit': - # val = (0, 1, 0) if self.lobby.use_team_colors: val = self.lobby.teams[self._selected_team_index].color else: @@ -819,7 +763,6 @@ class Chooser: return self._player def _update_icon(self) -> None: - from ba import _gameutils if self._profilenames[self._profileindex] == '_edit': tex = _ba.gettexture('black') tint_tex = _ba.gettexture('black') @@ -830,13 +773,12 @@ class Chooser: return try: - tex_name = (_ba.app.spaz_appearances[self.character_names[ - self.character_index]].icon_texture) - tint_tex_name = (_ba.app.spaz_appearances[self.character_names[ - self.character_index]].icon_mask_texture) + tex_name = (_ba.app.spaz_appearances[self._character_names[ + self._character_index]].icon_texture) + tint_tex_name = (_ba.app.spaz_appearances[self._character_names[ + self._character_index]].icon_mask_texture) except Exception: - from ba import _error - _error.print_exception('Error updating char icon list') + print_exception('Error updating char icon list') tex_name = 'neoSpazIcon' tint_tex_name = 'neoSpazIconColorMask' @@ -853,7 +795,7 @@ class Chooser: # If we're initing, flash. if not self._inited: - _gameutils.animate_array(self.icon, 'color', 3, { + animate_array(self.icon, 'color', 3, { 0.15: (1, 1, 1), 0.25: (2, 2, 2), 0.35: (1, 1, 1) @@ -861,7 +803,7 @@ class Chooser: # Blend in teams mode; switch instantly in ffa-mode. if can_switch_teams: - _gameutils.animate_array(self.icon, 'tint_color', 3, { + animate_array(self.icon, 'tint_color', 3, { 0: self.icon.tint_color, 0.1: clr }) @@ -963,10 +905,9 @@ class Lobby: for chooser in self.choosers: try: chooser.reload_profiles() - chooser.update_from_player_profiles() + chooser.update_from_profile() except Exception: - from ba import _error - _error.print_exception('error reloading profiles') + print_exception('Error reloading profiles.') def update_positions(self) -> None: """Update positions for all choosers.""" @@ -1005,11 +946,9 @@ class Lobby: self.choosers.remove(chooser) break if not found: - from ba import _error - _error.print_error(f'remove_chooser did not find player {player}') + print_error(f'remove_chooser did not find player {player}') elif chooser in self.choosers: - from ba import _error - _error.print_error(f'chooser remains after removal for {player}') + print_error(f'chooser remains after removal for {player}') self.update_positions() def remove_all_choosers(self) -> None: diff --git a/assets/src/ba_data/python/ba/_powerup.py b/assets/src/ba_data/python/ba/_powerup.py index 0e3d2d83..950cc9ce 100644 --- a/assets/src/ba_data/python/ba/_powerup.py +++ b/assets/src/ba_data/python/ba/_powerup.py @@ -44,14 +44,14 @@ class PowerupMessage: The type of powerup to be granted (a string). See ba.Powerup.poweruptype for available type values. - source_node + sourcenode The node the powerup game from, or None otherwise. If a powerup is accepted, a ba.PowerupAcceptMessage should be sent - back to the source_node to inform it of the fact. This will generally + back to the sourcenode to inform it of the fact. This will generally cause the powerup box to make a sound and disappear or whatnot. """ poweruptype: str - source_node: Optional[ba.Node] = None + sourcenode: Optional[ba.Node] = None @dataclass diff --git a/assets/src/ba_data/python/ba/_session.py b/assets/src/ba_data/python/ba/_session.py index e46d7a10..cf9fb06a 100644 --- a/assets/src/ba_data/python/ba/_session.py +++ b/assets/src/ba_data/python/ba/_session.py @@ -224,12 +224,12 @@ class Session: # Print a rejection message *only* to the client trying to # join (prevents spamming everyone else in the game). _ba.playsound(_ba.getsound('error')) - _ba.screenmessage( - Lstr(resource='playerLimitReachedText', - subs=[('${COUNT}', str(self.max_players))]), - color=(0.8, 0.0, 0.0), - clients=[player.get_input_device().client_id], - transient=True) + _ba.screenmessage(Lstr(resource='playerLimitReachedText', + subs=[('${COUNT}', + str(self.max_players))]), + color=(0.8, 0.0, 0.0), + clients=[player.inputdevice.client_id], + transient=True) return False _ba.playsound(_ba.getsound('dripity')) diff --git a/assets/src/ba_data/python/bastd/actor/bomb.py b/assets/src/ba_data/python/bastd/actor/bomb.py index 068dd7c9..cb2e9025 100644 --- a/assets/src/ba_data/python/bastd/actor/bomb.py +++ b/assets/src/ba_data/python/bastd/actor/bomb.py @@ -601,7 +601,7 @@ class Blast(ba.Actor): self.node.delete() elif isinstance(msg, ExplodeHitMessage): - node = ba.getcollision().opposing_node + node = ba.getcollision().opposingnode assert self.node nodepos = self.node.position mag = 2000.0 @@ -843,7 +843,7 @@ class Bomb(ba.Actor): self.handlemessage(ba.DieMessage()) def _handle_impact(self) -> None: - node = ba.getcollision().opposing_node + node = ba.getcollision().opposingnode # If we're an impact bomb and we came from this node, don't explode... # alternately if we're hitting another impact-bomb from the same @@ -875,7 +875,7 @@ class Bomb(ba.Actor): lambda: _safesetattr(self.node, 'stick_to_owner', True)) def _handle_splat(self) -> None: - node = ba.getcollision().opposing_node + node = ba.getcollision().opposingnode if (node is not self.owner and ba.time() - self._last_sticky_sound_time > 1.0): self._last_sticky_sound_time = ba.time() diff --git a/assets/src/ba_data/python/bastd/actor/controlsguide.py b/assets/src/ba_data/python/bastd/actor/controlsguide.py index 3b54729d..a149ba75 100644 --- a/assets/src/ba_data/python/bastd/actor/controlsguide.py +++ b/assets/src/ba_data/python/bastd/actor/controlsguide.py @@ -264,16 +264,14 @@ class ControlsGuide(ba.Actor): # If we have a touchscreen, we only fade in if we have a player with # an input device that is *not* the touchscreen. - touchscreen: Optional[ba.InputDevice] = _ba.get_input_device( + touchscreen: Optional[ba.InputDevice] = _ba.getinputdevice( 'TouchScreen', '#1', doraise=False) if touchscreen is not None: # We look at the session's players; not the activity's. # We want to get ones who are still in the process of # selecting a character, etc. - input_devices = [ - p.get_input_device() for p in ba.getsession().players - ] + input_devices = [p.inputdevice for p in ba.getsession().players] input_devices = [ i for i in input_devices if i and i is not touchscreen ] @@ -325,13 +323,13 @@ class ControlsGuide(ba.Actor): # We look at the session's players; not the activity's - we want to # get ones who are still in the process of selecting a character, etc. - input_devices = [p.get_input_device() for p in ba.getsession().players] + input_devices = [p.inputdevice for p in ba.getsession().players] input_devices = [i for i in input_devices if i] # If there's no players with input devices yet, try to default to # showing keyboard controls. if not input_devices: - kbd = _ba.get_input_device('Keyboard', '#1', doraise=False) + kbd = _ba.getinputdevice('Keyboard', '#1', doraise=False) if kbd is not None: input_devices.append(kbd) diff --git a/assets/src/ba_data/python/bastd/actor/powerupbox.py b/assets/src/ba_data/python/bastd/actor/powerupbox.py index 2bbe156b..77dc6ab9 100644 --- a/assets/src/ba_data/python/bastd/actor/powerupbox.py +++ b/assets/src/ba_data/python/bastd/actor/powerupbox.py @@ -305,9 +305,9 @@ class PowerupBox(ba.Actor): elif isinstance(msg, _TouchedMessage): if not self._powersgiven: - node = ba.getcollision().opposing_node + node = ba.getcollision().opposingnode node.handlemessage( - ba.PowerupMessage(self.poweruptype, source_node=self.node)) + ba.PowerupMessage(self.poweruptype, sourcenode=self.node)) elif isinstance(msg, ba.DieMessage): if self.node: diff --git a/assets/src/ba_data/python/bastd/actor/spaz.py b/assets/src/ba_data/python/bastd/actor/spaz.py index ba314e21..cf710b07 100644 --- a/assets/src/ba_data/python/bastd/actor/spaz.py +++ b/assets/src/ba_data/python/bastd/actor/spaz.py @@ -851,8 +851,8 @@ class Spaz(ba.Actor): self._num_times_hit = 0 self.node.handlemessage('flash') - if msg.source_node: - msg.source_node.handlemessage(ba.PowerupAcceptMessage()) + if msg.sourcenode: + msg.sourcenode.handlemessage(ba.PowerupAcceptMessage()) return True elif isinstance(msg, ba.FreezeMessage): @@ -1143,7 +1143,7 @@ class Spaz(ba.Actor): elif isinstance(msg, PunchHitMessage): if not self.node: return None - node = ba.getcollision().opposing_node + node = ba.getcollision().opposingnode # Only allow one hit per node per punch. if node and (node not in self._punched_nodes): @@ -1204,23 +1204,23 @@ class Spaz(ba.Actor): try: collision = ba.getcollision() - opposing_node = collision.opposing_node - opposing_body = collision.opposing_body + opposingnode = collision.opposingnode + opposingbody = collision.opposingbody except ba.NotFoundError: return True # Don't allow picking up of invincible dudes. try: - if opposing_node.invincible: + if opposingnode.invincible: return True except Exception: pass # If we're grabbing the pelvis of a non-shattered spaz, we wanna # grab the torso instead. - if (opposing_node.getnodetype() == 'spaz' - and not opposing_node.shattered and opposing_body == 4): - opposing_body = 1 + if (opposingnode.getnodetype() == 'spaz' + and not opposingnode.shattered and opposingbody == 4): + opposingbody = 1 # Special case - if we're holding a flag, don't replace it # (hmm - should make this customizable or more low level). @@ -1229,8 +1229,8 @@ class Spaz(ba.Actor): return True # Note: hold_body needs to be set before hold_node. - self.node.hold_body = opposing_body - self.node.hold_node = opposing_node + self.node.hold_body = opposingbody + self.node.hold_node = opposingnode elif isinstance(msg, ba.CelebrateMessage): if self.node: self.node.handlemessage('celebrate', int(msg.duration * 1000)) diff --git a/assets/src/ba_data/python/bastd/game/assault.py b/assets/src/ba_data/python/bastd/game/assault.py index 7f2d814d..0020bb0c 100644 --- a/assets/src/ba_data/python/bastd/game/assault.py +++ b/assets/src/ba_data/python/bastd/game/assault.py @@ -179,7 +179,7 @@ class AssaultGame(ba.TeamGameActivity[Player, Team]): ba.timer(length, light.delete) def _handle_base_collide(self, team: Team) -> None: - player = ba.getcollision().opposing_node.getdelegate( + player = ba.getcollision().opposingnode.getdelegate( PlayerSpaz, True).getplayer(Player) if not player or not player.is_alive(): return diff --git a/assets/src/ba_data/python/bastd/game/capturetheflag.py b/assets/src/ba_data/python/bastd/game/capturetheflag.py index 55d59a8e..43ff82e4 100644 --- a/assets/src/ba_data/python/bastd/game/capturetheflag.py +++ b/assets/src/ba_data/python/bastd/game/capturetheflag.py @@ -158,9 +158,9 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): self._scoreboard = Scoreboard() self._alarmsound = ba.getsound('alarm') self._ticking_sound = ba.getsound('ticking') - self._last_score_time = 0 self._score_sound = ba.getsound('score') self._swipsound = ba.getsound('swip') + self._last_score_time = 0 self._all_bases_material = ba.Material() self._last_home_flag_notice_print_time = 0.0 self._score_to_win = int(settings['Score to Win']) @@ -237,20 +237,22 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): )) # Other parts of our spazzes don't collide with our flags at all. - spaz_mat_no_flag_collide.add_actions(conditions=('they_have_material', - flagmat), - actions=('modify_part_collision', - 'collide', False)) + spaz_mat_no_flag_collide.add_actions( + conditions=('they_have_material', flagmat), + actions=('modify_part_collision', 'collide', False), + ) # We wanna know when *any* flag enters/leaves our base. base_region_mat.add_actions( conditions=('they_have_material', FlagFactory.get().flagmaterial), - actions=(('modify_part_collision', 'collide', - True), ('modify_part_collision', 'physical', False), - ('call', 'at_connect', - lambda: self._handle_flag_entered_base(team)), - ('call', 'at_disconnect', - lambda: self._handle_flag_left_base(team)))) + actions=( + ('modify_part_collision', 'collide', True), + ('modify_part_collision', 'physical', False), + ('call', 'at_connect', + lambda: self._handle_flag_entered_base(team)), + ('call', 'at_disconnect', + lambda: self._handle_flag_left_base(team)), + )) return team @@ -274,7 +276,7 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): ba.playsound(self._swipsound, position=team.flag.node.position) def _handle_flag_entered_base(self, team: Team) -> None: - flag = CTFFlag.from_node(ba.getcollision().opposing_node) + flag = CTFFlag.from_node(ba.getcollision().opposingnode) if not flag: print('Unable to get flag in _handle_flag_entered_base') return @@ -362,7 +364,7 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): self._flash_base(team) self._update_scoreboard() - # Have teammates celebrate + # Have teammates celebrate. for player in team.players: if player.actor: player.actor.handlemessage(ba.CelebrateMessage(2.0)) @@ -385,7 +387,7 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): def _handle_flag_left_base(self, team: Team) -> None: cur_time = ba.time() try: - flag = CTFFlag.from_node(ba.getcollision().opposing_node) + flag = CTFFlag.from_node(ba.getcollision().opposingnode) except ba.NodeNotFoundError: # We still get this call even if the flag stopped touching us # because it was deleted; that's ok. @@ -447,11 +449,12 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): return None if delegate is None else delegate.getplayer(Player) def _handle_hit_own_flag(self, team: Team, val: int) -> None: + """Called when a player touches their own team flag. + + We keep track of when each player is touching their + own flag so we can award points when returned. """ - keep track of when each player is touching their - own flag so we can award points when returned - """ - player = self._player_from_node(ba.getcollision().source_node) + player = self._player_from_node(ba.getcollision().sourcenode) if player: player.touching_own_flag += (1 if val else -1) @@ -464,7 +467,7 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): # Use a node message to kill the flag instead of just killing # our team's. (avoids redundantly killing new flags if # multiple body parts generate callbacks in one step). - node = ba.getcollision().opposing_node + node = ba.getcollision().opposingnode self._award_players_touching_own_flag(team) node.handlemessage(ba.DieMessage()) @@ -484,8 +487,7 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): team.touch_return_timer = None team.touch_return_timer_ticking = None if team.flag_return_touches < 0: - ba.print_error( - "CTF: flag_return_touches < 0; this shouldn't happen.") + ba.print_error('CTF flag_return_touches < 0') def _flash_base(self, team: Team, length: float = 2.0) -> None: light = ba.newnode('light', @@ -536,13 +538,15 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): self._score_to_win) def handlemessage(self, msg: Any) -> Any: + if isinstance(msg, ba.PlayerDiedMessage): - # Augment standard behavior. - super().handlemessage(msg) + super().handlemessage(msg) # Augment standard behavior. self.respawn_player(msg.getplayer(Player)) + elif isinstance(msg, FlagDiedMessage): assert isinstance(msg.flag, CTFFlag) ba.timer(0.1, ba.Call(self._spawn_flag_for_team, msg.flag.team)) + elif isinstance(msg, FlagPickedUpMessage): # Store the last player to hold the flag for scoring purposes. assert isinstance(msg.flag, CTFFlag) @@ -550,9 +554,11 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]): PlayerSpaz, True).getplayer(Player) msg.flag.held_count += 1 msg.flag.reset_return_times() + elif isinstance(msg, FlagDroppedMessage): # Store the last player to hold the flag for scoring purposes. assert isinstance(msg.flag, CTFFlag) msg.flag.held_count -= 1 + else: super().handlemessage(msg) diff --git a/assets/src/ba_data/python/bastd/game/chosenone.py b/assets/src/ba_data/python/bastd/game/chosenone.py index 63cb7401..3100dd5c 100644 --- a/assets/src/ba_data/python/bastd/game/chosenone.py +++ b/assets/src/ba_data/python/bastd/game/chosenone.py @@ -177,7 +177,7 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]): # If we have a chosen one, ignore these. if self._get_chosen_one_player() is not None: return - player = ba.getcollision().opposing_node.getdelegate( + player = ba.getcollision().opposingnode.getdelegate( PlayerSpaz, True).getplayer(Player) if player is not None and player.is_alive(): self._set_chosen_one_player(player) diff --git a/assets/src/ba_data/python/bastd/game/conquest.py b/assets/src/ba_data/python/bastd/game/conquest.py index a9511482..0f1e51e9 100644 --- a/assets/src/ba_data/python/bastd/game/conquest.py +++ b/assets/src/ba_data/python/bastd/game/conquest.py @@ -235,9 +235,9 @@ class ConquestGame(ba.TeamGameActivity[Player, Team]): def _handle_flag_player_collide(self) -> None: collision = ba.getcollision() - flag = collision.source_node.getdelegate(ConquestFlag) - player = collision.opposing_node.getdelegate(PlayerSpaz, - True).getplayer(Player) + flag = collision.sourcenode.getdelegate(ConquestFlag) + player = collision.opposingnode.getdelegate(PlayerSpaz, + True).getplayer(Player) if not flag or not player: return assert flag.light diff --git a/assets/src/ba_data/python/bastd/game/easteregghunt.py b/assets/src/ba_data/python/bastd/game/easteregghunt.py index a03b40ce..ae0c0f48 100644 --- a/assets/src/ba_data/python/bastd/game/easteregghunt.py +++ b/assets/src/ba_data/python/bastd/game/easteregghunt.py @@ -137,9 +137,9 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]): if self.has_ended(): return collision = ba.getcollision() - egg = collision.source_node.getdelegate(Egg) - player = collision.opposing_node.getdelegate(PlayerSpaz, - True).getplayer(Player) + egg = collision.sourcenode.getdelegate(Egg) + player = collision.opposingnode.getdelegate(PlayerSpaz, + True).getplayer(Player) if player and egg: player.team.score += 1 diff --git a/assets/src/ba_data/python/bastd/game/football.py b/assets/src/ba_data/python/bastd/game/football.py index a9b472e0..c428a64d 100644 --- a/assets/src/ba_data/python/bastd/game/football.py +++ b/assets/src/ba_data/python/bastd/game/football.py @@ -209,7 +209,7 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]): assert self._flag is not None if self._flag.scored: return - region = ba.getcollision().source_node + region = ba.getcollision().sourcenode i = None for i in range(len(self._score_regions)): if region == self._score_regions[i].node: @@ -654,7 +654,7 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): return # See which score region it was. - region = ba.getcollision().source_node + region = ba.getcollision().sourcenode i = None for i in range(len(self._score_regions)): if region == self._score_regions[i].node: diff --git a/assets/src/ba_data/python/bastd/game/hockey.py b/assets/src/ba_data/python/bastd/game/hockey.py index 805480ef..26af046c 100644 --- a/assets/src/ba_data/python/bastd/game/hockey.py +++ b/assets/src/ba_data/python/bastd/game/hockey.py @@ -245,9 +245,9 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]): def _handle_puck_player_collide(self) -> None: collision = ba.getcollision() - puck = collision.source_node.getdelegate(Puck) - player = collision.opposing_node.getdelegate(PlayerSpaz, - True).getplayer(Player) + puck = collision.sourcenode.getdelegate(Puck) + player = collision.opposingnode.getdelegate(PlayerSpaz, + True).getplayer(Player) if player and puck: puck.last_players_to_touch[player.team.id] = player @@ -265,7 +265,7 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]): if self._puck.scored: return - region = ba.getcollision().source_node + region = ba.getcollision().sourcenode index = 0 for index in range(len(self._score_regions)): if region == self._score_regions[index].node: diff --git a/assets/src/ba_data/python/bastd/game/kingofthehill.py b/assets/src/ba_data/python/bastd/game/kingofthehill.py index e1a4cef5..4b11464c 100644 --- a/assets/src/ba_data/python/bastd/game/kingofthehill.py +++ b/assets/src/ba_data/python/bastd/game/kingofthehill.py @@ -247,7 +247,7 @@ class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]): ba.playsound(self._swipsound) def _handle_player_flag_region_collide(self, colliding: bool) -> None: - player = ba.getcollision().opposing_node.getdelegate( + player = ba.getcollision().opposingnode.getdelegate( PlayerSpaz, True).getplayer(Player) if not player: return diff --git a/assets/src/ba_data/python/bastd/game/race.py b/assets/src/ba_data/python/bastd/game/race.py index ecb6e38f..df6de7bf 100644 --- a/assets/src/ba_data/python/bastd/game/race.py +++ b/assets/src/ba_data/python/bastd/game/race.py @@ -227,9 +227,9 @@ class RaceGame(ba.TeamGameActivity[Player, Team]): # pylint: disable=too-many-branches # pylint: disable=too-many-nested-blocks collision = ba.getcollision() - region = collision.source_node.getdelegate(RaceRegion) - playerspaz = collision.opposing_node.getdelegate(PlayerSpaz, - doraise=False) + region = collision.sourcenode.getdelegate(RaceRegion) + playerspaz = collision.opposingnode.getdelegate(PlayerSpaz, + doraise=False) player = playerspaz.getplayer(Player) if playerspaz else None if not player or not region: return diff --git a/assets/src/ba_data/python/bastd/game/runaround.py b/assets/src/ba_data/python/bastd/game/runaround.py index fbcce6e7..d5d0637c 100644 --- a/assets/src/ba_data/python/bastd/game/runaround.py +++ b/assets/src/ba_data/python/bastd/game/runaround.py @@ -406,7 +406,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): ba.timer(2.0, self._start_updating_waves) def _handle_reached_end(self) -> None: - spaz = ba.getcollision().opposing_node.getdelegate(SpazBot, True) + spaz = ba.getcollision().opposingnode.getdelegate(SpazBot, True) if not spaz.is_alive(): return # Ignore bodies flying in. diff --git a/assets/src/ba_data/python/bastd/ui/settings/controls.py b/assets/src/ba_data/python/bastd/ui/settings/controls.py index 00e81723..9805557e 100644 --- a/assets/src/ba_data/python/bastd/ui/settings/controls.py +++ b/assets/src/ba_data/python/bastd/ui/settings/controls.py @@ -91,7 +91,7 @@ class ControlsSettingsWindow(ba.Window): height += space_height show_keyboard = False - if _ba.get_input_device('Keyboard', '#1', doraise=False) is not None: + if _ba.getinputdevice('Keyboard', '#1', doraise=False) is not None: show_keyboard = True height += spacing * 2 show_keyboard_p2 = False if app.vr_mode else show_keyboard @@ -393,7 +393,7 @@ class ControlsSettingsWindow(ba.Window): self._save_state() ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.main_menu_window = (keyboard.ConfigKeyboardWindow( - _ba.get_input_device('Keyboard', '#1')).get_root_widget()) + _ba.getinputdevice('Keyboard', '#1')).get_root_widget()) def _config_keyboard2(self) -> None: # pylint: disable=cyclic-import @@ -401,7 +401,7 @@ class ControlsSettingsWindow(ba.Window): self._save_state() ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.main_menu_window = (keyboard.ConfigKeyboardWindow( - _ba.get_input_device('Keyboard', '#2')).get_root_widget()) + _ba.getinputdevice('Keyboard', '#2')).get_root_widget()) def _do_mobile_devices(self) -> None: # pylint: disable=cyclic-import diff --git a/docs/ba_module.md b/docs/ba_module.md index 281527df..c7da0733 100644 --- a/docs/ba_module.md +++ b/docs/ba_module.md @@ -1,5 +1,5 @@ -

last updated on 2020-05-25 for Ballistica version 1.5.0 build 20027

+

last updated on 2020-05-25 for Ballistica version 1.5.0 build 20028

This page documents the Python classes and functions in the 'ba' module, which are the ones most relevant to modding in Ballistica. If you come across something you feel should be included here or could be better explained, please let me know. Happy modding!


@@ -189,6 +189,7 @@
+

ba.DelegateNotFoundError

+

Inherits from: ba.NotFoundError, Exception, BaseException

+

Exception raised when an expected delegate object does not exist.

+ +

Category: Exception Classes +

+ +

Methods:

+

<all methods inherited from ba.NotFoundError>

+

ba.Dependency

Inherits from: typing.Generic

A dependency on a DependencyComponent (with an optional config).

@@ -3569,8 +3580,8 @@ loc.connectattr('position', light, 'position')

delete()

delete(ignore_missing: bool = True) -> None

-

Delete the node. Ignores already-deleted nodes unless ignore_missing -is False, in which case an Exception is thrown.

+

Delete the node. Ignores already-deleted nodes if ignore_missing +is True; otherwise a ba.NodeNotFoundError is thrown.

exists()

@@ -3598,7 +3609,7 @@ the right thing both for Node objects and values of None.

If the node has no delegate or it is not an instance of the passed type, then None will be returned. If 'doraise' is True, then an -Exception will be raised instead in such cases.

+ba.DelegateNotFoundError will be raised instead.

getnodetype()

@@ -4094,7 +4105,7 @@ the type-checker properly identifies the returned value as one.

This message is normally received by touching a ba.PowerupBox.

Attributes:

-
poweruptype, source_node
+
poweruptype, sourcenode

poweruptype

str

@@ -4102,11 +4113,11 @@ the type-checker properly identifies the returned value as one.

See ba.Powerup.poweruptype for available type values.

-

source_node

+

sourcenode

Optional[ba.Node]

The node the powerup game from, or None otherwise. If a powerup is accepted, a ba.PowerupAcceptMessage should be sent -back to the source_node to inform it of the fact. This will generally +back to the sourcenode to inform it of the fact. This will generally cause the powerup box to make a sound and disappear or whatnot.

@@ -4114,7 +4125,7 @@ cause the powerup box to make a sound and disappear or whatnot.

Methods:

<constructor>

-

ba.PowerupMessage(poweruptype: str, source_node: Optional[ba.Node] = None)

+

ba.PowerupMessage(poweruptype: str, sourcenode: Optional[ba.Node] = None)

@@ -4443,7 +4454,7 @@ that a SessionPlayer is still present if retaining references to one for any length of time.

Attributes:

-
character, color, gamedata, gameplayer, highlight, id, in_game, sessiondata, team
+
character, color, gamedata, gameplayer, highlight, id, in_game, inputdevice, sessiondata, team

character

str

@@ -4490,6 +4501,11 @@ the right thing both for Player objects and values of None.

This bool value will be True once the Player has completed any lobby character/team selection.

+
+

inputdevice

+

ba.InputDevice

+

The input device associated with the player.

+

sessiondata

Dict

@@ -4507,7 +4523,7 @@ is still in its lobby selecting a team/etc. then a

Methods:

-
assign_input_call(), exists(), get_account_id(), get_icon(), get_input_device(), get_name(), remove_from_game(), reset_input(), set_name()
+
assign_input_call(), exists(), get_account_id(), get_icon(), get_name(), remove_from_game(), reset_input(), set_name()

assign_input_call()

assign_input_call(type: Union[str, Tuple[str, ...]], @@ -4544,12 +4560,6 @@ joins (while verification occurs).

Returns the character's icon (images, colors, etc contained in a dict)

-
-

get_input_device()

-

get_input_device() -> ba.InputDevice

- -

Returns the player's input device.

-

get_name()

get_name(full: bool = False, icon: bool = True) -> str