diff --git a/.efrocachemap b/.efrocachemap index 9f34ff7b..f420e66d 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/6d/a5/bc48ad0c1b5757913b8d354e4302", - "build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/94/e0/cd115dbd1ce795e9b6a2878e8912", - "build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/44/78/d3166e9e3f2f443c13838768b4ee", - "build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/79/af/4d26abbac53e9fc396d1fc5660ae", - "build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/e0/85/8d8d8d74685d0823bc341942c31c", - "build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/34/81/06f6dff6c5686d1b2ffb1b44bb46", - "build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/34/96/f1d361405a41d118016a576ef517", - "build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/0c/7e/116fdd2bb269fd3c4c3826f526b9", - "build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/75/a6/320d0a4b79a1e0c0cb8fecbc69e2", - "build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/8a/de/f35f0be58d20cc58cf1ba078013a", - "build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/2b/23/849c8e6286a8de4f6140f249c59a", - "build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/ba/fd/49fe8a41b0448e2fd81a462618cb" + "build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/4c/7f/785a205dbe19ee4099f427dd9ca2", + "build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/b3/10/7ab16ca078a401bbbe2310660b85", + "build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/ef/f5/be729c0261c6326c3ef92330b764", + "build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/98/9d/98bc183187af679fbc6531e3ac04", + "build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/17/07/aed82d9bc9c5b4dd3457fcea9a8a", + "build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/71/5e/a837ebb98b829cd3bebf081e02b0", + "build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/9c/a0/3204f264abfd13f40f7f4f72aa65", + "build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/68/ab/de21be17db5b93a7191c353dbce8", + "build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/54/5a/e32b46e614ed37c7ebe42c73b03c", + "build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/15/09/2245bdaf317da30c7d63d8559c82", + "build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/f6/45/6972dd74de4fc822a28e995a4a0d", + "build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/34/74/e8ff832e808ceaa07a4015fb2a5f" } \ No newline at end of file diff --git a/assets/src/ba_data/python/_ba.py b/assets/src/ba_data/python/_ba.py index e7c48f86..4fb9bba5 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=289441941088504861465847265420796017643 +# SOURCES_HASH=102801821147286215106809880997032542189 # I'm sorry Pylint. I know this file saddens you. Be strong. # pylint: disable=useless-suppression diff --git a/assets/src/ba_data/python/ba/__init__.py b/assets/src/ba_data/python/ba/__init__.py index 26bdf607..d257eca7 100644 --- a/assets/src/ba_data/python/ba/__init__.py +++ b/assets/src/ba_data/python/ba/__init__.py @@ -73,7 +73,7 @@ from ba._appconfig import AppConfig from ba._appdelegate import AppDelegate from ba._apputils import is_browser_likely_available from ba._campaign import Campaign -from ba._gameutils import (animate, animate_array, show_damage_count, +from ba._gameutils import (GameTip, animate, animate_array, show_damage_count, timestring, cameraflash) from ba._general import (WeakCall, Call, existing, Existable, verify_object_death, storagename) diff --git a/assets/src/ba_data/python/ba/_activity.py b/assets/src/ba_data/python/ba/_activity.py index 0fa0d50e..08795ab9 100644 --- a/assets/src/ba_data/python/ba/_activity.py +++ b/assets/src/ba_data/python/ba/_activity.py @@ -169,12 +169,8 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): # FIXME: Relocate or remove the need for this stuff. self.paused_text: Optional[ba.Actor] = None - self.spaz_respawn_icons_right: Dict[int, RespawnIcon] - session = _ba.getsession() - if session is None: - raise RuntimeError('No current session') - self._session = weakref.ref(session) + self._session = weakref.ref(_ba.getsession()) # Preloaded data for actors, maps, etc; indexed by type. self.preloads: Dict[Type, Any] = {} @@ -193,12 +189,6 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): self._delay_delete_teams: List[TeamType] = [] self._players_that_left: List[ReferenceType[PlayerType]] = [] self._teams_that_left: List[ReferenceType[TeamType]] = [] - - # This gets set once another activity has begun transitioning in but - # before this one is killed. The on_transition_out() method is also - # called at this time. Make sure to not assign player inputs, - # change music, or anything else with global implications once this - # happens. self._transitioning_out = False # A handy place to put most actors; this list is pruned of dead @@ -209,7 +199,6 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): self._last_prune_dead_actors_time = _ba.time() self._prune_dead_actors_timer: Optional[ba.Timer] = None - # This stuff gets filled in just before on_begin() is called. self.teams = [] self.players = [] diff --git a/assets/src/ba_data/python/ba/_appdelegate.py b/assets/src/ba_data/python/ba/_appdelegate.py index 148b5587..e1cf5431 100644 --- a/assets/src/ba_data/python/ba/_appdelegate.py +++ b/assets/src/ba_data/python/ba/_appdelegate.py @@ -36,14 +36,14 @@ class AppDelegate: def create_default_game_settings_ui( self, gameclass: Type[ba.GameActivity], - sessionclass: Type[ba.Session], config: Optional[dict], + sessiontype: Type[ba.Session], settings: Optional[dict], completion_call: Callable[[Optional[dict]], None]) -> None: """Launch a UI to configure the given game config. It should manipulate the contents of config and call completion_call when done. """ - del gameclass, sessionclass, config, completion_call # unused + del gameclass, sessiontype, settings, completion_call # Unused. from ba import _error _error.print_error( "create_default_game_settings_ui needs to be overridden") diff --git a/assets/src/ba_data/python/ba/_coopsession.py b/assets/src/ba_data/python/ba/_coopsession.py index 926d1cc9..8f57b9b8 100644 --- a/assets/src/ba_data/python/ba/_coopsession.py +++ b/assets/src/ba_data/python/ba/_coopsession.py @@ -312,7 +312,7 @@ class CoopSession(Session): # Skip players that are still choosing a team. if player.in_game: - self.stats.register_player(player) + self.stats.register_sessionplayer(player) self.stats.setactivity(next_game) # Now flip the current activity. diff --git a/assets/src/ba_data/python/ba/_gameactivity.py b/assets/src/ba_data/python/ba/_gameactivity.py index a6320fa8..c83e6ea6 100644 --- a/assets/src/ba_data/python/ba/_gameactivity.py +++ b/assets/src/ba_data/python/ba/_gameactivity.py @@ -54,7 +54,8 @@ class GameActivity(Activity[PlayerType, TeamType]): """ # pylint: disable=too-many-public-methods - tips: List[Union[str, Dict[str, Any]]] = [] + # Tips to be presented to the user at the start of the game. + tips: List[Union[str, ba.GameTip]] = [] # Default getname() will return this if not None. name: Optional[str] = None @@ -82,16 +83,16 @@ class GameActivity(Activity[PlayerType, TeamType]): @classmethod def create_settings_ui( cls, - sessionclass: Type[ba.Session], + sessiontype: Type[ba.Session], settings: Optional[dict], completion_call: Callable[[Optional[dict]], None], ) -> None: """Launch an in-game UI to configure settings for a game type. - 'sessionclass' should be the ba.Session class the game will be used in. + 'sessiontype' should be the ba.Session class the game will be used in. - 'config' should be an existing config dict (specifies 'edit' ui mode) - or None (specifies 'add' ui mode). + 'settings' should be an existing settings dict (implies 'edit' + ui mode) or None (implies 'add' ui mode). 'completion_call' will be called with a filled-out settings dict on success or None on cancel. @@ -103,14 +104,14 @@ class GameActivity(Activity[PlayerType, TeamType]): """ delegate = _ba.app.delegate assert delegate is not None - delegate.create_default_game_settings_ui(cls, sessionclass, settings, + delegate.create_default_game_settings_ui(cls, sessiontype, settings, completion_call) @classmethod def getscoreconfig(cls) -> ba.ScoreConfig: """Return info about game scoring setup; can be overridden by games.""" - return cls.scoreconfig if cls.scoreconfig is not None else ScoreConfig( - ) + return (cls.scoreconfig + if cls.scoreconfig is not None else ScoreConfig()) @classmethod def getname(cls) -> str: @@ -170,62 +171,8 @@ class GameActivity(Activity[PlayerType, TeamType]): @classmethod def get_available_settings( cls, sessiontype: Type[ba.Session]) -> List[ba.Setting]: - """ - Called by the default ba.GameActivity.create_settings_ui() - implementation; should return a dict of config options to be presented - to the user for the given ba.Session type. - - The format for settings is a list of 2-member tuples consisting - of a name and a dict of options. - - Available Setting Options: - - 'default': This determines the default value as well as the - type (int, float, or bool) - - 'min_value': Minimum value for int/float settings. - - 'max_value': Maximum value for int/float settings. - - 'choices': A list of name/value pairs the user can choose from by name. - - 'increment': Value increment for int/float settings. - - # example get_available_settings() for a capture-the-flag game: - @classmethod - def get_available_settings(cls, sessiontype): - return [("Score to Win", { - 'default': 3, - 'min_value': 1 - }), - ("Flag Touch Return Time", { - 'default': 0, - 'min_value': 0, - 'increment': 1 - }), - ("Flag Idle Return Time", { - 'default': 30, - 'min_value': 5, - 'increment': 5 - }), - ("Time Limit", { - 'default': 0, - 'choices': [ - ('None', 0), ('1 Minute', 60), ('2 Minutes', 120), - ('5 Minutes', 300), ('10 Minutes', 600), - ('20 Minutes', 1200) - ] - }), - ("Respawn Times", { - 'default': 1.0, - 'choices': [ - ('Shorter', 0.25), ('Short', 0.5), ('Normal', 1.0), - ('Long', 2.0), ('Longer', 4.0) - ] - }), - ("Epic Mode", { - 'default': False - })] + """Return a list of settings relevant to this game type when + running under the provided session type. """ del sessiontype # Unused arg. return [] if cls.available_settings is None else cls.available_settings @@ -403,7 +350,6 @@ class GameActivity(Activity[PlayerType, TeamType]): return '' def on_transition_in(self) -> None: - super().on_transition_in() # Make our map. @@ -575,8 +521,9 @@ class GameActivity(Activity[PlayerType, TeamType]): victim_player=player, importance=importance, showpoints=self.show_kill_points) - return None - return super().handlemessage(msg) + else: + return super().handlemessage(msg) + return None def _show_scoreboard_info(self) -> None: """Create the game info display. @@ -599,7 +546,7 @@ class GameActivity(Activity[PlayerType, TeamType]): else: sb_desc_l = sb_desc_in if not isinstance(sb_desc_l[0], str): - raise TypeError('Invalid format for instance description') + raise TypeError('Invalid format for instance description.') is_empty = (sb_desc_l[0] == '') subs = [] @@ -608,9 +555,7 @@ class GameActivity(Activity[PlayerType, TeamType]): translation = Lstr(translate=('gameDescriptions', sb_desc_l[0]), subs=subs) sb_desc = translation - vrmode = _ba.app.vr_mode - yval = -34 if is_empty else -20 yval -= 16 sbpos = ((15, yval) if isinstance(self.session, FreeForAllSession) else @@ -727,7 +672,7 @@ class GameActivity(Activity[PlayerType, TeamType]): def _show_tip(self) -> None: # pylint: disable=too-many-locals - from ba._gameutils import animate + from ba._gameutils import animate, GameTip from ba._enums import SpecialChar # If there's any tips left on the list, display one. @@ -735,14 +680,12 @@ class GameActivity(Activity[PlayerType, TeamType]): tip = self.tips.pop(random.randrange(len(self.tips))) tip_title = Lstr(value='${A}:', subs=[('${A}', Lstr(resource='tipText'))]) - icon = None - sound = None - if isinstance(tip, dict): - if 'icon' in tip: - icon = tip['icon'] - if 'sound' in tip: - sound = tip['sound'] - tip = tip['tip'] + icon: Optional[ba.Texture] = None + sound: Optional[ba.Sound] = None + if isinstance(tip, GameTip): + icon = tip.icon + sound = tip.sound + tip = tip.text assert isinstance(tip, str) # Do a few substitutions. @@ -844,12 +787,11 @@ class GameActivity(Activity[PlayerType, TeamType]): super().end(results, delay, force) def end_game(self) -> None: - """ - Tells the game to wrap itself up and call ba.Activity.end() - immediately. This method should be overridden by subclasses. + """Tell the game to wrap up and call ba.Activity.end() immediately. - A game should always be prepared to end and deliver results, even if - there is no 'winner' yet; this way things like the standard time-limit + This method should be overridden by subclasses. A game should always + be prepared to end and deliver results, even if there is no 'winner' + yet; this way things like the standard time-limit (ba.GameActivity.setup_standard_time_limit()) will work with the game. """ print('WARNING: default end_game() implementation called;' diff --git a/assets/src/ba_data/python/ba/_gameresults.py b/assets/src/ba_data/python/ba/_gameresults.py index 8b850f80..1ffa78a8 100644 --- a/assets/src/ba_data/python/ba/_gameresults.py +++ b/assets/src/ba_data/python/ba/_gameresults.py @@ -84,8 +84,7 @@ class GameResults: sessionteam = team.sessionteam self._scores[sessionteam.id] = (weakref.ref(sessionteam), score) - def get_team_score(self, - sessionteam: Union[ba.SessionTeam]) -> Optional[int]: + def get_team_score(self, sessionteam: ba.SessionTeam) -> Optional[int]: """Return the score for a given ba.SessionTeam.""" for score in list(self._scores.values()): if score[0]() is sessionteam: @@ -111,7 +110,7 @@ class GameResults: """Return whether there is a score for a given team.""" return any(s[0]() is sessionteam for s in self._scores.values()) - def get_team_score_str(self, team: ba.Team) -> ba.Lstr: + def get_team_score_str(self, sessionteam: ba.SessionTeam) -> ba.Lstr: """Return the score for the given ba.Team as an Lstr. (properly formatted for the score type.) @@ -123,7 +122,7 @@ class GameResults: if not self._game_set: raise RuntimeError("Can't get team-score-str until game is set.") for score in list(self._scores.values()): - if score[0]() is team.sessionteam: + if score[0]() is sessionteam: if score[1] is None: return Lstr(value='-') if self._scoretype is ScoreType.SECONDS: diff --git a/assets/src/ba_data/python/ba/_gameutils.py b/assets/src/ba_data/python/ba/_gameutils.py index 8c48ce76..7f4588fb 100644 --- a/assets/src/ba_data/python/ba/_gameutils.py +++ b/assets/src/ba_data/python/ba/_gameutils.py @@ -22,6 +22,7 @@ from __future__ import annotations +from dataclasses import dataclass from typing import TYPE_CHECKING import _ba @@ -42,6 +43,17 @@ TROPHY_CHARS = { } +@dataclass +class GameTip: + """Defines a tip presentable to the user at the start of a game. + + Category: Gameplay Classes + """ + text: str + icon: Optional[ba.Texture] = None + sound: Optional[ba.Sound] = None + + def get_trophy_string(trophy_id: str) -> str: """Given a trophy id, returns a string to visualize it.""" if trophy_id in TROPHY_CHARS: diff --git a/assets/src/ba_data/python/ba/_general.py b/assets/src/ba_data/python/ba/_general.py index bb1f3bb5..43eff04b 100644 --- a/assets/src/ba_data/python/ba/_general.py +++ b/assets/src/ba_data/python/ba/_general.py @@ -123,7 +123,7 @@ def json_prep(data: Any) -> Any: def utf8_all(data: Any) -> Any: - """Convert any unicode data in provided sequence(s)to utf8 bytes.""" + """Convert any unicode data in provided sequence(s) to utf8 bytes.""" if isinstance(data, dict): return dict((utf8_all(key), utf8_all(value)) for key, value in list(data.items())) diff --git a/assets/src/ba_data/python/ba/_multiteamsession.py b/assets/src/ba_data/python/ba/_multiteamsession.py index 90bd4b1f..9daedc9d 100644 --- a/assets/src/ba_data/python/ba/_multiteamsession.py +++ b/assets/src/ba_data/python/ba/_multiteamsession.py @@ -224,7 +224,7 @@ class MultiTeamSession(Session): except NotFoundError: has_team = False if has_team: - self.stats.register_player(player) + self.stats.register_sessionplayer(player) self.stats.setactivity(next_game) # Now flip the current activity. diff --git a/assets/src/ba_data/python/ba/_player.py b/assets/src/ba_data/python/ba/_player.py index a07bc61d..6dbd6969 100644 --- a/assets/src/ba_data/python/ba/_player.py +++ b/assets/src/ba_data/python/ba/_player.py @@ -26,7 +26,8 @@ from dataclasses import dataclass from typing import TYPE_CHECKING, TypeVar, Generic, cast import _ba -from ba._error import SessionPlayerNotFoundError, print_exception +from ba._error import (SessionPlayerNotFoundError, print_exception, + ActorNotFoundError) from ba._messages import DeathType, DieMessage if TYPE_CHECKING: @@ -219,11 +220,12 @@ class Player(Generic[TeamType]): def position(self) -> ba.Vec3: """The position of the player, as defined by its current ba.Actor. - This value is undefined when the player has no Actor. + If the player currently has no actor, raises a ba.ActorNotFoundError. """ assert self._postinited assert not self._expired - assert self.actor is not None + if self.actor is None: + raise ActorNotFoundError return _ba.Vec3(self.node.position) def exists(self) -> bool: diff --git a/assets/src/ba_data/python/ba/_session.py b/assets/src/ba_data/python/ba/_session.py index 2025cc42..72c56101 100644 --- a/assets/src/ba_data/python/ba/_session.py +++ b/assets/src/ba_data/python/ba/_session.py @@ -84,6 +84,10 @@ class Session: Whether players should be allowed to join in the middle of activities. + customdata + A shared dictionary for objects to use as storage on this session. + Ensure that keys here are unique to avoid collisions. + """ use_teams: bool = False use_team_colors: bool = True @@ -95,6 +99,7 @@ class Session: max_players: int min_players: int players: List[ba.SessionPlayer] + customdata: dict teams: List[ba.SessionTeam] def __init__(self, @@ -166,6 +171,7 @@ class Session: self.min_players = min_players self.max_players = max_players + self.customdata = {} self._in_set_activity = False self._next_team_id = 0 self._activity_retained: Optional[ba.Activity] = None @@ -695,7 +701,7 @@ class Session: color=chooser.get_color(), highlight=chooser.get_highlight()) - self.stats.register_player(sessionplayer) + self.stats.register_sessionplayer(sessionplayer) if pass_to_activity: activity.add_player(sessionplayer) return sessionplayer diff --git a/assets/src/ba_data/python/ba/_stats.py b/assets/src/ba_data/python/ba/_stats.py index b92dc374..317e8c83 100644 --- a/assets/src/ba_data/python/ba/_stats.py +++ b/assets/src/ba_data/python/ba/_stats.py @@ -28,7 +28,7 @@ from dataclasses import dataclass import _ba from ba._error import (print_exception, print_error, SessionTeamNotFoundError, - SessionPlayerNotFoundError) + SessionPlayerNotFoundError, NotFoundError) if TYPE_CHECKING: import ba @@ -61,8 +61,8 @@ class PlayerRecord: """ character: str - def __init__(self, name: str, name_full: str, player: ba.SessionPlayer, - stats: ba.Stats): + def __init__(self, name: str, name_full: str, + sessionplayer: ba.SessionPlayer, stats: ba.Stats): self.name = name self.name_full = name_full self.score = 0 @@ -75,10 +75,10 @@ class PlayerRecord: self._multi_kill_count = 0 self._stats = weakref.ref(stats) self._last_sessionplayer: Optional[ba.SessionPlayer] = None - self._player: Optional[ba.SessionPlayer] = None - self._team: Optional[ReferenceType[ba.SessionTeam]] = None + self._sessionplayer: Optional[ba.SessionPlayer] = None + self._sessionteam: Optional[ReferenceType[ba.SessionTeam]] = None self.streak = 0 - self.associate_with_player(player) + self.associate_with_sessionplayer(sessionplayer) @property def team(self) -> ba.SessionTeam: @@ -87,8 +87,8 @@ class PlayerRecord: This can still return a valid result even if the player is gone. Raises a ba.SessionTeamNotFoundError if the team no longer exists. """ - assert self._team is not None - team = self._team() + assert self._sessionteam is not None + team = self._sessionteam() if team is None: raise SessionTeamNotFoundError() return team @@ -100,9 +100,9 @@ class PlayerRecord: Raises a ba.SessionPlayerNotFoundError if the player no longer exists. """ - if not self._player: + if not self._sessionplayer: raise SessionPlayerNotFoundError() - return self._player + return self._sessionplayer def getname(self, full: bool = False) -> str: """Return the player entry's name.""" @@ -127,19 +127,20 @@ class PlayerRecord: return stats.getactivity() return None - def associate_with_player(self, sessionplayer: ba.SessionPlayer) -> None: + def associate_with_sessionplayer(self, + sessionplayer: ba.SessionPlayer) -> None: """Associate this entry with a ba.SessionPlayer.""" - self._team = weakref.ref(sessionplayer.sessionteam) + self._sessionteam = weakref.ref(sessionplayer.sessionteam) self.character = sessionplayer.character self._last_sessionplayer = sessionplayer - self._player = sessionplayer + self._sessionplayer = sessionplayer self.streak = 0 def _end_multi_kill(self) -> None: self._multi_kill_timer = None self._multi_kill_count = 0 - def get_last_player(self) -> ba.SessionPlayer: + def get_last_sessionplayer(self) -> ba.SessionPlayer: """Return the last ba.Player we were associated with.""" assert self._last_sessionplayer is not None return self._last_sessionplayer @@ -204,18 +205,20 @@ class PlayerRecord: # Only award this if they're still alive and we can get # a current position for them. - our_pos: Optional[Sequence[float]] = None - if self._player is not None: - if self._player.activityplayer is not None: - if self._player.activityplayer.node: - our_pos = self._player.activityplayer.node.position + our_pos: Optional[ba.Vec3] = None + if self._sessionplayer is not None: + if self._sessionplayer.activityplayer is not None: + try: + our_pos = self._sessionplayer.activityplayer.position + except NotFoundError: + pass if our_pos is None: return # Jitter position a bit since these often come in clusters. - our_pos = (our_pos[0] + (random.random() - 0.5) * 2.0, - our_pos[1] + (random.random() - 0.5) * 2.0, - our_pos[2] + (random.random() - 0.5) * 2.0) + our_pos = _ba.Vec3(our_pos[0] + (random.random() - 0.5) * 2.0, + our_pos[1] + (random.random() - 0.5) * 2.0, + our_pos[2] + (random.random() - 0.5) * 2.0) activity = self.getactivity() if activity is not None: PopupText(Lstr( @@ -305,14 +308,14 @@ class Stats: s_player.accum_killed_count = 0 s_player.streak = 0 - def register_player(self, player: ba.SessionPlayer) -> None: - """Register a player with this score-set.""" + def register_sessionplayer(self, player: ba.SessionPlayer) -> None: + """Register a ba.SessionPlayer with this score-set.""" assert player.exists() # Invalid refs should never be passed to funcs. name = player.getname() if name in self._player_records: # If the player already exists, update his character and such as # it may have changed. - self._player_records[name].associate_with_player(player) + self._player_records[name].associate_with_sessionplayer(player) else: name_full = player.getname(full=True) self._player_records[name] = PlayerRecord(name, name_full, player, @@ -325,7 +328,7 @@ class Stats: # Go through our player records and return ones whose player id still # corresponds to a player with that name. for record_id, record in self._player_records.items(): - lastplayer = record.get_last_player() + lastplayer = record.get_last_sessionplayer() if lastplayer and lastplayer.getname() == record_id: records[record_id] = record return records diff --git a/assets/src/ba_data/python/bastd/activity/multiteamscore.py b/assets/src/ba_data/python/bastd/activity/multiteamscore.py index c95266d1..68a4c3d8 100644 --- a/assets/src/ba_data/python/bastd/activity/multiteamscore.py +++ b/assets/src/ba_data/python/bastd/activity/multiteamscore.py @@ -97,7 +97,7 @@ class MultiTeamScoreScreenActivity(ScoreScreenActivity): if is_free_for_all and results is not None: assert isinstance(results, ba.GameResults) assert p_rec.team.gameteam is not None - val = results.get_team_score_str(p_rec.team.gameteam) + val = results.get_team_score_str(p_rec.team) assert val is not None return val return str(p_rec.accumscore) diff --git a/assets/src/ba_data/python/bastd/appdelegate.py b/assets/src/ba_data/python/bastd/appdelegate.py index c3e19ee0..351a1984 100644 --- a/assets/src/ba_data/python/bastd/appdelegate.py +++ b/assets/src/ba_data/python/bastd/appdelegate.py @@ -34,7 +34,7 @@ class AppDelegate(ba.AppDelegate): def create_default_game_settings_ui( self, gameclass: Type[ba.GameActivity], - sessionclass: Type[ba.Session], config: Optional[dict], + sessiontype: Type[ba.Session], settings: Optional[dict], completion_call: Callable[[Optional[dict]], Any]) -> None: """(internal)""" @@ -42,6 +42,6 @@ class AppDelegate(ba.AppDelegate): from bastd.ui.playlist.editgame import PlaylistEditGameWindow prev_window = ba.app.main_menu_window ba.app.main_menu_window = (PlaylistEditGameWindow( - gameclass, sessionclass, config, + gameclass, sessiontype, settings, completion_call=completion_call).get_root_widget()) ba.containerwidget(edit=prev_window, transition='out_left') diff --git a/assets/src/ba_data/python/bastd/game/onslaught.py b/assets/src/ba_data/python/bastd/game/onslaught.py index 607ce182..cbb03d17 100644 --- a/assets/src/ba_data/python/bastd/game/onslaught.py +++ b/assets/src/ba_data/python/bastd/game/onslaught.py @@ -141,7 +141,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): name = 'Onslaught' description = 'Defeat all enemies.' - tips: List[Union[str, Dict[str, Any]]] = [ + tips: List[Union[str, ba.GameTip]] = [ 'Hold any button to run.' ' (Trigger buttons work well if you have them)', 'Try tricking enemies into killing eachother or running off cliffs.', @@ -213,43 +213,45 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): def on_transition_in(self) -> None: super().on_transition_in() - session = ba.getsession() + customdata = ba.getsession().customdata # Show special landmine tip on rookie preset. if self._preset in {Preset.ROOKIE, Preset.ROOKIE_EASY}: # Show once per session only (then we revert to regular tips). - if not getattr(session, '_g_showed_onslaught_landmine_tip', False): - setattr(session, '_g_showed_onslaught_landmine_tip', True) - self.tips = [{ - 'tip': 'Land-mines are a good way' - ' to stop speedy enemies.', - 'icon': ba.gettexture('powerupLandMines'), - 'sound': ba.getsound('ding') - }] + if not customdata.get('_showed_onslaught_landmine_tip', False): + customdata['_showed_onslaught_landmine_tip'] = True + self.tips = [ + ba.GameTip( + 'Land-mines are a good way to stop speedy enemies.', + icon=ba.gettexture('powerupLandMines'), + sound=ba.getsound('ding')) + ] # Show special tnt tip on pro preset. if self._preset in {Preset.PRO, Preset.PRO_EASY}: # Show once per session only (then we revert to regular tips). - if not getattr(session, '_g_showed_onslaught_tnt_tip', False): - setattr(session, '_g_showed_onslaught_tnt_tip', True) - self.tips = [{ - 'tip': 'Take out a group of enemies by\n' - 'setting off a bomb near a TNT box.', - 'icon': ba.gettexture('tnt'), - 'sound': ba.getsound('ding') - }] + if not customdata.get('_showed_onslaught_tnt_tip', False): + customdata['_showed_onslaught_tnt_tip'] = True + self.tips = [ + ba.GameTip( + 'Take out a group of enemies by\n' + 'setting off a bomb near a TNT box.', + icon=ba.gettexture('tnt'), + sound=ba.getsound('ding')) + ] # Show special curse tip on uber preset. if self._preset in {Preset.UBER, Preset.UBER_EASY}: # Show once per session only (then we revert to regular tips). - if not getattr(session, '_g_showed_onslaught_curse_tip', False): - setattr(session, '_g_showed_onslaught_curse_tip', True) - self.tips = [{ - 'tip': 'Curse boxes turn you into a ticking time bomb.\n' - 'The only cure is to quickly grab a health-pack.', - 'icon': ba.gettexture('powerupCurse'), - 'sound': ba.getsound('ding') - }] + if not customdata.get('_showed_onslaught_curse_tip', False): + customdata['_showed_onslaught_curse_tip'] = True + self.tips = [ + ba.GameTip( + 'Curse boxes turn you into a ticking time bomb.\n' + 'The only cure is to quickly grab a health-pack.', + icon=ba.gettexture('powerupCurse'), + sound=ba.getsound('ding')) + ] self._spawn_info_text = ba.NodeActor( ba.newnode('text', diff --git a/docs/ba_module.md b/docs/ba_module.md index c3fadfde..662e9975 100644 --- a/docs/ba_module.md +++ b/docs/ba_module.md @@ -1,5 +1,5 @@ -
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!
create_default_game_settings_ui(self, gameclass: Type[ba.GameActivity], sessionclass: Type[ba.Session], config: Optional[dict], completion_call: Callable[[Optional[dict]], None]) -> None
+create_default_game_settings_ui(self, gameclass: Type[ba.GameActivity], sessiontype: Type[ba.Session], settings: Optional[dict], completion_call: Callable[[Optional[dict]], None]) -> None
Launch a UI to configure the given game config.
@@ -1733,7 +1734,7 @@ and it should begin its actual game logic. high score lists.The position of the player, as defined by its current ba.Actor.
-This value is undefined when the player has no Actor.
+If the player currently has no actor, raises a ba.ActorNotFoundError.
create_settings_ui(sessionclass: Type[ba.Session], settings: Optional[dict], completion_call: Callable[[Optional[dict]], None]) -> None
+create_settings_ui(sessiontype: Type[ba.Session], settings: Optional[dict], completion_call: Callable[[Optional[dict]], None]) -> None
Launch an in-game UI to configure settings for a game type.
-'sessionclass' should be the ba.Session class the game will be used in.
+'sessiontype' should be the ba.Session class the game will be used in.
-'config' should be an existing config dict (specifies 'edit' ui mode) - or None (specifies 'add' ui mode).
+'settings' should be an existing settings dict (implies 'edit' + ui mode) or None (implies 'add' ui mode).
'completion_call' will be called with a filled-out settings dict on success or None on cancel.
@@ -2439,11 +2440,11 @@ will replace the old.end_game(self) -> None
-Tells the game to wrap itself up and call ba.Activity.end() -immediately. This method should be overridden by subclasses.
+Tell the game to wrap up and call ba.Activity.end() immediately.
-A game should always be prepared to end and deliver results, even if -there is no 'winner' yet; this way things like the standard time-limit +
This method should be overridden by subclasses. A game should always +be prepared to end and deliver results, even if there is no 'winner' +yet; this way things like the standard time-limit (ba.GameActivity.setup_standard_time_limit()) will work with the game.
get_available_settings(sessiontype: Type[ba.Session]) -> List[ba.Setting]
-Called by the default ba.GameActivity.create_settings_ui() -implementation; should return a dict of config options to be presented -to the user for the given ba.Session type.
- -The format for settings is a list of 2-member tuples consisting -of a name and a dict of options.
- -Available Setting Options:
- -'default': This determines the default value as well as the - type (int, float, or bool)
- -'min_value': Minimum value for int/float settings.
- -'max_value': Maximum value for int/float settings.
- -'choices': A list of name/value pairs the user can choose from by name.
- -'increment': Value increment for int/float settings.
- -# example get_available_settings() for a capture-the-flag game:
-@classmethod
-def get_available_settings(cls, sessiontype):
- return [("Score to Win", {
- 'default': 3,
- 'min_value': 1
- }),
- ("Flag Touch Return Time", {
- 'default': 0,
- 'min_value': 0,
- 'increment': 1
- }),
- ("Flag Idle Return Time", {
- 'default': 30,
- 'min_value': 5,
- 'increment': 5
- }),
- ("Time Limit", {
- 'default': 0,
- 'choices': [
- ('None', 0), ('1 Minute', 60), ('2 Minutes', 120),
- ('5 Minutes', 300), ('10 Minutes', 600),
- ('20 Minutes', 1200)
- ]
- }),
- ("Respawn Times", {
- 'default': 1.0,
- 'choices': [
- ('Shorter', 0.25), ('Short', 0.5), ('Normal', 1.0),
- ('Long', 2.0), ('Longer', 4.0)
- ]
- }),
- ("Epic Mode", {
- 'default': False
- })]
+Return a list of settings relevant to this game type when +running under the provided session type.
get_team_score(self, sessionteam: Union[ba.SessionTeam]) -> Optional[int]
+get_team_score(self, sessionteam: ba.SessionTeam) -> Optional[int]
Return the score for a given ba.SessionTeam.
get_team_score_str(self, team: ba.Team) -> ba.Lstr
+get_team_score_str(self, sessionteam: ba.SessionTeam) -> ba.Lstr
Return the score for the given ba.Team as an Lstr.
@@ -2854,6 +2802,22 @@ Results for a completed game.This can be a number or None. (see the none_is_winner arg in the constructor)
+<top level class> +
+Defines a tip presentable to the user at the start of a game.
+ +Category: Gameplay Classes +
+ +ba.GameTip(text: str, icon: Optional[ba.Texture] = None, sound: Optional[ba.Sound] = None)
+The position of the player, as defined by its current ba.Actor.
-This value is undefined when the player has no Actor.
+If the player currently has no actor, raises a ba.ActorNotFoundError.
ba.PlayerRecord(name: str, name_full: str, player: ba.SessionPlayer, stats: ba.Stats)
+ba.PlayerRecord(name: str, name_full: str, sessionplayer: ba.SessionPlayer, stats: ba.Stats)
associate_with_player(self, sessionplayer: ba.SessionPlayer) -> None
+associate_with_sessionplayer(self, sessionplayer: ba.SessionPlayer) -> None
Associate this entry with a ba.SessionPlayer.
@@ -4470,8 +4434,8 @@ the type-checker properly identifies the returned value as one.Get the icon for this instance's player.
get_last_player(self) -> ba.SessionPlayer
+get_last_sessionplayer(self) -> ba.SessionPlayer
Return the last ba.Player we were associated with.
@@ -4700,13 +4664,19 @@ Pass 0 or a negative number for no ban time. maintaining state between them (players, teams, score tallies, etc).bool
Whether players should be allowed to join in the middle of activities.
+dict
+A shared dictionary for objects to use as storage on this session. +Ensure that keys here are unique to avoid collisions.
+ba.Stats()
@@ -5324,10 +5294,10 @@ of the session.Should be called when a player is killed.
register_player(self, player: ba.SessionPlayer) -> None
+register_sessionplayer(self, player: ba.SessionPlayer) -> None
-Register a player with this score-set.
+Register a ba.SessionPlayer with this score-set.