diff --git a/.efrocachemap b/.efrocachemap index 62ce73ba..6ff19b51 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/63/2c/d64f68f0e4ecbb55f3731ad95530", - "build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/86/cd/c8c9b3e4a265ac816c5d249e4638", - "build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/29/92/c312438bf1dbdb599bd020a5c220", - "build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/68/75/6686aa5b6e0f88e1a40d444fd013", - "build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/c1/bc/23b3f5d0929adf8c497accad24bb", - "build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/81/07/d95b4d6354acf4003f70307ba7cb", - "build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/2c/bd/18dff8eda46be2d2edeee0a7ece9", - "build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/8d/64/a0beab66455ed6be694583fb6d53", - "build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/a2/ba/1e71e85b38eed56a55f8e0d85821", - "build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/dd/38/d98994fed8732ab774e879b0c8a5", - "build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/2c/e0/97ebbdf740a3bfbc5324f510204e", - "build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/4e/69/2af7295ec87bcf480e57f41e19a7" + "build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/15/7a/d4e39ad022b8365418ecee4d026b", + "build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ab/ad/7e371dfb7ed7b10d46f0bf9497d8", + "build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/92/28/faa6501d779b381dec7fb9ac19c5", + "build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/45/57/97d03c6230cfc4d9f0687249f408", + "build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/d3/e1/a3a86c40804f1e724c59ee04f1a2", + "build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ae/78/7a6522e860506fe1046808ce7b0b", + "build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/08/71/09be3ad2f8cf99f885549a7296a0", + "build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/87/49/3c1d3c8a995df400e46df3470b02", + "build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/43/d4/48ec85af308fd027d7dd06eaa650", + "build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/cc/1b/303089d3c1f3b7620c632ee864c3", + "build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/f2/55/890f1b498ccf5988dee573d5f3ca", + "build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/b9/b1/d96d693c3ad52cedd4f517fd9d94" } \ No newline at end of file diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml index f8e28694..1763add7 100644 --- a/.idea/dictionaries/ericf.xml +++ b/.idea/dictionaries/ericf.xml @@ -398,6 +398,7 @@ curstate curtime curtimestr + customdata customizebrowser cutscenes cval @@ -604,6 +605,7 @@ factoryclass fallbacks farthestpt + fback fbase fclose fcmd @@ -874,6 +876,7 @@ icns iconpicker iconscale + iconsstorename ident idevices ifeq @@ -910,6 +913,7 @@ infotextcolor inidividual initializers + initialplayerinfos initing inits inmobi @@ -1110,6 +1114,8 @@ mapselect maptype markupbase + masktex + masktexstorename mathmodule mathnode mathutils @@ -1163,6 +1169,7 @@ moduledir modulefinder modulename + modulepath modutils moola mopaque @@ -1204,6 +1211,7 @@ myhome myinput mylist + mymodule mynode myobj myprojname @@ -1214,6 +1222,7 @@ mypytype mysound mytextnode + mythingie myweakcall mywidget namedarg @@ -1360,6 +1369,7 @@ pkgutil playercast playerdata + playerinfos playerlostspaz playernode playerpt @@ -1533,6 +1543,7 @@ qrcode qrencode qual + qualname quoprimime rando randommodule @@ -1786,8 +1797,10 @@ steelseries stickman storable + storagename storedhash storeitemui + storename strftime stringprep stringptr @@ -1913,6 +1926,7 @@ thanvannispen thelaststand themself + thingie threadtype throwiness timedisplay @@ -2095,6 +2109,8 @@ willeval wincfg wincount + winnergroup + winnergroups winplt winprj winref diff --git a/assets/src/ba_data/python/_ba.py b/assets/src/ba_data/python/_ba.py index f33fbb95..fb7554fd 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=247084187994045015646460724420234222242 +# SOURCES_HASH=214847179904844500339904334878206016957 # I'm sorry Pylint. I know this file saddens you. Be strong. # pylint: disable=useless-suppression @@ -822,16 +822,11 @@ class SessionPlayer: This bool value will be True once the Player has completed any lobby character/team selection. - team: ba.SessionTeam + sessionteam: ba.SessionTeam The ba.SessionTeam this Player is on. If the SessionPlayer is still in its lobby selecting a team/etc. then a ba.SessionTeamNotFoundError will be raised. - sessiondata: Dict - A dict for use by the current ba.Session for - storing data associated with this player. - This persists for the duration of the session. - inputdevice: ba.InputDevice The input device associated with the player. @@ -853,8 +848,7 @@ class SessionPlayer: """ id: int in_game: bool - team: ba.SessionTeam - sessiondata: Dict + sessionteam: ba.SessionTeam inputdevice: ba.InputDevice color: Sequence[float] highlight: Sequence[float] diff --git a/assets/src/ba_data/python/ba/__init__.py b/assets/src/ba_data/python/ba/__init__.py index 3a2d949a..936cdef6 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._campaign import Campaign from ba._gameutils import (animate, animate_array, show_damage_count, timestring, cameraflash) from ba._general import (WeakCall, Call, existing, Existable, - verify_object_death) + verify_object_death, storagename) from ba._level import Level from ba._lobby import Lobby, Chooser from ba._math import normalized_color, is_point_in_box, vec3validate diff --git a/assets/src/ba_data/python/ba/_activity.py b/assets/src/ba_data/python/ba/_activity.py index c5c5b8f7..6c494c5f 100644 --- a/assets/src/ba_data/python/ba/_activity.py +++ b/assets/src/ba_data/python/ba/_activity.py @@ -215,7 +215,7 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): self.lobby = None self._stats: Optional[ba.Stats] = None - self._gamedata: Optional[dict] = {} + self._customdata: Optional[dict] = {} def __del__(self) -> None: @@ -265,15 +265,15 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): """ @property - def gamedata(self) -> dict: + def customdata(self) -> dict: """Entities needing to store simple data with an activity can put it here. This dict will be deleted when the activity expires, so contained objects generally do not need to worry about handling expired activities. """ assert not self._expired - assert isinstance(self._gamedata, dict) - return self._gamedata + assert isinstance(self._customdata, dict) + return self._customdata @property def expired(self) -> bool: @@ -574,9 +574,9 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): def add_player(self, sessionplayer: ba.SessionPlayer) -> None: """(internal)""" - assert sessionplayer.team is not None + assert sessionplayer.sessionteam is not None sessionplayer.resetinput() - sessionteam = sessionplayer.team + sessionteam = sessionplayer.sessionteam assert sessionplayer in sessionteam.players team = sessionteam.gameteam assert team is not None @@ -608,7 +608,7 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): player: Any = sessionplayer.gameplayer assert isinstance(player, self._playertype) - team: Any = sessionplayer.team.gameteam + team: Any = sessionplayer.sessionteam.gameteam assert isinstance(team, self._teamtype) assert player in team.players @@ -795,9 +795,9 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): print_exception(f'Error in Activity on_expire() for {self}.') try: - self._gamedata = None + self._customdata = None except Exception: - print_exception(f'Error clearing gamedata for {self}.') + print_exception(f'Error clearing customdata for {self}.') # Don't want to be holding any delay-delete refs at this point. self._prune_delay_deletes() diff --git a/assets/src/ba_data/python/ba/_activitytypes.py b/assets/src/ba_data/python/ba/_activitytypes.py index afca3247..cb03c75c 100644 --- a/assets/src/ba_data/python/ba/_activitytypes.py +++ b/assets/src/ba_data/python/ba/_activitytypes.py @@ -27,8 +27,8 @@ import _ba from ba._activity import Activity from ba._music import setmusic, MusicType # False positive due to our class_generics_filter custom pylint filter. -from ba._player import Player # pylint: disable=W0611 -from ba._team import Team # pylint: disable=W0611 +from ba import _player +from ba import _team if TYPE_CHECKING: from typing import Any, Dict, Optional @@ -36,6 +36,17 @@ if TYPE_CHECKING: from ba._lobby import JoinInfo +# Even though we don't need custom Player/Team types, define empty ones +# so we don't have ba.Player[Any] and ba.Team[Any] as our types which +# reduces type safety. +class Player(_player.Player['Team']): + """Our player type for this game.""" + + +class Team(_team.Team[Player]): + """Our team type for this game.""" + + class EndSessionActivity(Activity[Player, Team]): """Special ba.Activity to fade out and end the current ba.Session.""" @@ -158,7 +169,7 @@ class ScoreScreenActivity(Activity[Player, Team]): self._custom_continue_message: Optional[ba.Lstr] = None self._server_transitioning: Optional[bool] = None - def on_player_join(self, player: ba.Player) -> None: + def on_player_join(self, player: Player) -> None: from ba import _general super().on_player_join(player) time_till_assign = max( @@ -224,7 +235,7 @@ class ScoreScreenActivity(Activity[Player, Team]): # Otherwise end the activity normally. self.end() - def _safe_assign(self, player: ba.Player) -> None: + def _safe_assign(self, player: Player) -> None: # Just to be extra careful, don't assign if we're transitioning out. # (though theoretically that would be ok). diff --git a/assets/src/ba_data/python/ba/_coopsession.py b/assets/src/ba_data/python/ba/_coopsession.py index 052a77d0..22456491 100644 --- a/assets/src/ba_data/python/ba/_coopsession.py +++ b/assets/src/ba_data/python/ba/_coopsession.py @@ -341,37 +341,35 @@ class CoopSession(Session): self.setactivity(_ba.new_activity(TransitionActivity)) else: - player_info: List[ba.PlayerInfo] + playerinfos: List[ba.PlayerInfo] # Generic team games. if isinstance(results, TeamGameResults): - player_info = results.get_player_info() - score = results.get_team_score(results.get_teams()[0]) + playerinfos = results.playerinfos + score = results.get_team_score(results.sessionteams[0]) fail_message = None - score_order = ('decreasing' if results.get_lower_is_better() - else 'increasing') - if results.get_score_type() in (ScoreType.SECONDS, - ScoreType.MILLISECONDS): - score_type = 'time' + score_order = ('decreasing' + if results.lower_is_better else 'increasing') + if results.scoretype in (ScoreType.SECONDS, + ScoreType.MILLISECONDS): + scoretype = 'time' # ScoreScreen wants hundredths of a second. if score is not None: - if results.get_score_type() is ScoreType.SECONDS: + if results.scoretype is ScoreType.SECONDS: score *= 100 - elif (results.get_score_type() is - ScoreType.MILLISECONDS): + elif results.scoretype is ScoreType.MILLISECONDS: score //= 10 else: raise RuntimeError('FIXME') else: - if results.get_score_type() is not ScoreType.POINTS: - print(f'Unknown ScoreType:' - f' "{results.get_score_type()}"') - score_type = 'points' + if results.scoretype is not ScoreType.POINTS: + print(f'Unknown ScoreType:' f' "{results.scoretype}"') + scoretype = 'points' # Old coop-game-specific results; should migrate away from these. else: - player_info = results.get('player_info') + playerinfos = results.get('playerinfos') score = results['score'] if 'score' in results else None fail_message = (results['fail_message'] if 'fail_message' in results else None) @@ -380,12 +378,12 @@ class CoopSession(Session): activity_score_type = (activity.get_score_type() if isinstance( activity, CoopGameActivity) else None) assert activity_score_type is not None - score_type = activity_score_type + scoretype = activity_score_type # Validate types. - if player_info is not None: - assert isinstance(player_info, list) - assert (isinstance(i, PlayerInfo) for i in player_info) + if playerinfos is not None: + assert isinstance(playerinfos, list) + assert (isinstance(i, PlayerInfo) for i in playerinfos) # Looks like we were in a round - check the outcome and # go from there. @@ -397,11 +395,11 @@ class CoopSession(Session): self.setactivity( _ba.new_activity( CoopScoreScreen, { - 'player_info': player_info, + 'playerinfos': playerinfos, 'score': score, 'fail_message': fail_message, 'score_order': score_order, - 'score_type': score_type, + 'score_type': scoretype, 'outcome': outcome, 'campaign': self.campaign, 'level': self.campaign_level_name diff --git a/assets/src/ba_data/python/ba/_dualteamsession.py b/assets/src/ba_data/python/ba/_dualteamsession.py index 4cdef7de..c07e0451 100644 --- a/assets/src/ba_data/python/ba/_dualteamsession.py +++ b/assets/src/ba_data/python/ba/_dualteamsession.py @@ -52,18 +52,17 @@ class DualTeamSession(MultiTeamSession): TeamVictoryScoreScreenActivity) from bastd.activity.multiteamvictory import ( TeamSeriesVictoryScoreScreenActivity) - winners = results.get_winners() + winners = results.winnergroups # If everyone has the same score, call it a draw. if len(winners) < 2: self.setactivity(_ba.new_activity(DrawScoreScreenActivity)) else: winner = winners[0].teams[0] - winner.sessiondata['score'] += 1 + winner.customdata['score'] += 1 # If a team has won, show final victory screen. - if winner.sessiondata['score'] >= (self._series_length - - 1) / 2 + 1: + if winner.customdata['score'] >= (self._series_length - 1) / 2 + 1: self.setactivity( _ba.new_activity(TeamSeriesVictoryScoreScreenActivity, {'winner': winner})) diff --git a/assets/src/ba_data/python/ba/_freeforallsession.py b/assets/src/ba_data/python/ba/_freeforallsession.py index f7b1eeef..71ac1088 100644 --- a/assets/src/ba_data/python/ba/_freeforallsession.py +++ b/assets/src/ba_data/python/ba/_freeforallsession.py @@ -76,7 +76,7 @@ class FreeForAllSession(MultiTeamSession): TeamSeriesVictoryScoreScreenActivity) from bastd.activity.freeforallvictory import ( FreeForAllVictoryScoreScreenActivity) - winners = results.get_winners() + winners = results.winnergroups # If there's multiple players and everyone has the same score, # call it a draw. @@ -91,20 +91,20 @@ class FreeForAllSession(MultiTeamSession): for i, winner in enumerate(winners): for team in winner.teams: points = (point_awards[i] if i in point_awards else 0) - team.sessiondata['previous_score'] = ( - team.sessiondata['score']) - team.sessiondata['score'] += points + team.customdata['previous_score'] = ( + team.customdata['score']) + team.customdata['score'] += points series_winners = [ team for team in self.teams - if team.sessiondata['score'] >= self._ffa_series_length + if team.customdata['score'] >= self._ffa_series_length ] series_winners.sort(reverse=True, - key=lambda tm: (tm.sessiondata['score'])) + key=lambda tm: (tm.customdata['score'])) if (len(series_winners) == 1 or (len(series_winners) > 1 - and series_winners[0].sessiondata['score'] != - series_winners[1].sessiondata['score'])): + and series_winners[0].customdata['score'] != + series_winners[1].customdata['score'])): self.setactivity( _ba.new_activity(TeamSeriesVictoryScoreScreenActivity, {'winner': series_winners[0]})) diff --git a/assets/src/ba_data/python/ba/_gameactivity.py b/assets/src/ba_data/python/ba/_gameactivity.py index 6ed91a80..370bc7a5 100644 --- a/assets/src/ba_data/python/ba/_gameactivity.py +++ b/assets/src/ba_data/python/ba/_gameactivity.py @@ -284,7 +284,7 @@ class GameActivity(Activity[PlayerType, TeamType]): # Holds some flattened info about the player set at the point # when on_begin() is called. - self.initial_player_info: Optional[List[ba.PlayerInfo]] = None + self.initialplayerinfos: Optional[List[ba.PlayerInfo]] = None # Go ahead and get our map loading. self._map_type = _map.get_map_class(self._calc_map_name(settings)) @@ -511,14 +511,14 @@ class GameActivity(Activity[PlayerType, TeamType]): _ba.timer(2.5, self._show_tip) # Store some basic info about players present at start time. - self.initial_player_info = [ + self.initialplayerinfos = [ PlayerInfo(name=p.getname(full=True), character=p.character) for p in self.players ] # Sort this by name so high score lists/etc will be consistent # regardless of player join order. - self.initial_player_info.sort(key=lambda x: x.name) + self.initialplayerinfos.sort(key=lambda x: x.name) # If this is a tournament, query info about it such as how much # time is left. @@ -890,9 +890,10 @@ class GameActivity(Activity[PlayerType, TeamType]): if player.actor and not self.has_ended(): from bastd.actor.respawnicon import RespawnIcon - player.gamedata['respawn_timer'] = _ba.Timer( + player.customdata['respawn_timer'] = _ba.Timer( respawn_time, WeakCall(self.spawn_player_if_exists, player)) - player.gamedata['respawn_icon'] = RespawnIcon(player, respawn_time) + player.customdata['respawn_icon'] = RespawnIcon( + player, respawn_time) def spawn_player_if_exists(self, player: PlayerType) -> None: """ diff --git a/assets/src/ba_data/python/ba/_gameresults.py b/assets/src/ba_data/python/ba/_gameresults.py index ff185d56..c7009ff4 100644 --- a/assets/src/ba_data/python/ba/_gameresults.py +++ b/assets/src/ba_data/python/ba/_gameresults.py @@ -26,8 +26,6 @@ import weakref from dataclasses import dataclass from typing import TYPE_CHECKING -from ba._team import Team - if TYPE_CHECKING: from weakref import ReferenceType from typing import Sequence, Tuple, Any, Optional, Dict, List, Union @@ -56,56 +54,54 @@ class TeamGameResults: self._game_set = False self._scores: Dict[int, Tuple[ReferenceType[ba.SessionTeam], Optional[int]]] = {} - self._teams: Optional[List[ReferenceType[ba.SessionTeam]]] = None - self._player_info: Optional[List[ba.PlayerInfo]] = None + self._sessionteams: Optional[List[ReferenceType[ + ba.SessionTeam]]] = None + self._playerinfos: Optional[List[ba.PlayerInfo]] = None self._lower_is_better: Optional[bool] = None self._score_label: Optional[str] = None self._none_is_winner: Optional[bool] = None - self._score_type: Optional[ba.ScoreType] = None + self._scoretype: Optional[ba.ScoreType] = None def set_game(self, game: ba.GameActivity) -> None: """Set the game instance these results are applying to.""" if self._game_set: raise RuntimeError('Game set twice for TeamGameResults.') self._game_set = True - self._teams = [weakref.ref(team) for team in game.teams] + self._sessionteams = [weakref.ref(team) for team in game.teams] score_info = game.get_score_info() - self._player_info = copy.deepcopy(game.initial_player_info) + self._playerinfos = copy.deepcopy(game.initialplayerinfos) self._lower_is_better = score_info.lower_is_better self._score_label = score_info.label self._none_is_winner = score_info.none_is_winner - self._score_type = score_info.scoretype + self._scoretype = score_info.scoretype - def set_team_score(self, team: Union[ba.SessionTeam, ba.Team], - score: Optional[int]) -> None: + def set_team_score(self, team: ba.Team, score: Optional[int]) -> None: """Set the score for a given ba.Team. This can be a number or None. (see the none_is_winner arg in the constructor) """ - if isinstance(team, Team): - team = team.sessionteam - self._scores[team.id] = (weakref.ref(team), score) + sessionteam = team.sessionteam + self._scores[sessionteam.id] = (weakref.ref(sessionteam), score) - def get_team_score(self, team: Union[ba.SessionTeam, - ba.Team]) -> Optional[int]: - """Return the score for a given team.""" - if isinstance(team, Team): - team = team.sessionteam + def get_team_score(self, + sessionteam: Union[ba.SessionTeam]) -> Optional[int]: + """Return the score for a given ba.SessionTeam.""" for score in list(self._scores.values()): - if score[0]() is team: + if score[0]() is sessionteam: return score[1] # If we have no score value, assume None. return None - def get_teams(self) -> List[ba.SessionTeam]: + @property + def sessionteams(self) -> List[ba.SessionTeam]: """Return all ba.SessionTeams in the results.""" if not self._game_set: raise RuntimeError("Can't get teams until game is set.") teams = [] - assert self._teams is not None - for team_ref in self._teams: + assert self._sessionteams is not None + for team_ref in self._sessionteams: team = team_ref() if team is not None: teams.append(team) @@ -130,55 +126,61 @@ class TeamGameResults: if score[0]() is team.sessionteam: if score[1] is None: return Lstr(value='-') - if self._score_type is ScoreType.SECONDS: + if self._scoretype is ScoreType.SECONDS: return timestring(score[1] * 1000, centi=False, timeformat=TimeFormat.MILLISECONDS) - if self._score_type is ScoreType.MILLISECONDS: + if self._scoretype is ScoreType.MILLISECONDS: return timestring(score[1], centi=True, timeformat=TimeFormat.MILLISECONDS) return Lstr(value=str(score[1])) return Lstr(value='-') - def get_player_info(self) -> List[ba.PlayerInfo]: + @property + def playerinfos(self) -> List[ba.PlayerInfo]: """Get info about the players represented by the results.""" if not self._game_set: raise RuntimeError("Can't get player-info until game is set.") - assert self._player_info is not None - return self._player_info + assert self._playerinfos is not None + return self._playerinfos - def get_score_type(self) -> ba.ScoreType: - """Get the type of score.""" + @property + def scoretype(self) -> ba.ScoreType: + """The type of score.""" if not self._game_set: raise RuntimeError("Can't get score-type until game is set.") - assert self._score_type is not None - return self._score_type + assert self._scoretype is not None + return self._scoretype - def get_score_name(self) -> str: - """Get the name associated with scores ('points', etc).""" + @property + def score_label(self) -> str: + """The label associated with scores ('points', etc).""" if not self._game_set: - raise RuntimeError("Can't get score-name until game is set.") + raise RuntimeError("Can't get score-label until game is set.") assert self._score_label is not None return self._score_label - def get_lower_is_better(self) -> bool: - """Return whether lower scores are better.""" + @property + def lower_is_better(self) -> bool: + """Whether lower scores are better.""" if not self._game_set: raise RuntimeError("Can't get lower-is-better until game is set.") assert self._lower_is_better is not None return self._lower_is_better - def get_winning_team(self) -> Optional[ba.SessionTeam]: - """Get the winning ba.Team if there is exactly one; None otherwise.""" + @property + def winning_team(self) -> Optional[ba.SessionTeam]: + """The winning ba.SessionTeam if there is exactly one, or else None.""" if not self._game_set: raise RuntimeError("Can't get winners until game is set.") - winners = self.get_winners() + winners = self.winnergroups if winners and len(winners[0].teams) == 1: return winners[0].teams[0] return None - def get_winners(self) -> List[WinnerGroup]: + @property + def winnergroups(self) -> List[WinnerGroup]: """Get an ordered list of winner groups.""" if not self._game_set: raise RuntimeError("Can't get winners until game is set.") @@ -200,17 +202,18 @@ class TeamGameResults: results.sort(reverse=not self._lower_is_better, key=lambda x: x[0]) # Also group the 'None' scores. - none_teams: List[ba.SessionTeam] = [] + none_sessionteams: List[ba.SessionTeam] = [] for score in self._scores.values(): scoreteam = score[0]() if scoreteam is not None and score[1] is None: - none_teams.append(scoreteam) + none_sessionteams.append(scoreteam) # Add the Nones to the list (either as winners or losers # depending on the rules). - if none_teams: - nones: List[Tuple[Optional[int], - List[ba.SessionTeam]]] = [(None, none_teams)] + if none_sessionteams: + nones: List[Tuple[Optional[int], List[ba.SessionTeam]]] = [ + (None, none_sessionteams) + ] if self._none_is_winner: results = nones + results else: diff --git a/assets/src/ba_data/python/ba/_general.py b/assets/src/ba_data/python/ba/_general.py index 5ff0acd2..bb1f3bb5 100644 --- a/assets/src/ba_data/python/ba/_general.py +++ b/assets/src/ba_data/python/ba/_general.py @@ -25,6 +25,7 @@ import gc import types import weakref import random +import inspect from typing import TYPE_CHECKING, TypeVar from typing_extensions import Protocol @@ -332,7 +333,45 @@ def _verify_object_death(wref: ReferenceType) -> None: print(f'{Clr.YLW}Active References:{Clr.RST}') i = 1 for ref in refs: - # if isinstance(ref, types.FrameType): - # continue print(f'{Clr.YLW} reference {i}:{Clr.BLU} {ref}{Clr.RST}') i += 1 + + +def storagename(basename: str) -> str: + """Generate a (hopefully) unique name for storing things in public places. + + Category: General Utility Functions + + This consists of a leading underscore, the module path at the + call site with dots replaced by underscores, the class name, and + the provided suffix. When storing data in public places such as + 'customdata' dicts, this minimizes the chance of collisions if a + module or class is duplicated or renamed. + + # Example: generate a unique name for storage purposes: + class MyThingie: + + # This will give something like '_mymodule_submodule_mythingie_data'. + _STORENAME = ba.storagename('data') + + def __init__(self, activity): + # Store some data in the Activity we were passed + activity.customdata[self._STORENAME] = {} + """ + frame = inspect.currentframe() + if frame is None: + raise RuntimeError('Cannot get current stack frame.') + fback = frame.f_back + if fback is None: + raise RuntimeError('Cannot get parent stack frame.') + modulepath = fback.f_globals.get('__name__') + if modulepath is None: + raise RuntimeError('Cannot get parent stack module path.') + assert isinstance(modulepath, str) + qualname = fback.f_locals.get('__qualname__') + if qualname is not None: + assert isinstance(qualname, str) + fullpath = f'_{modulepath}_{qualname.lower()}_{basename}' + else: + fullpath = f'_{modulepath}_{basename}' + return fullpath.replace('.', '_') diff --git a/assets/src/ba_data/python/ba/_multiteamsession.py b/assets/src/ba_data/python/ba/_multiteamsession.py index 38016cf7..91a17e5c 100644 --- a/assets/src/ba_data/python/ba/_multiteamsession.py +++ b/assets/src/ba_data/python/ba/_multiteamsession.py @@ -156,7 +156,7 @@ class MultiTeamSession(Session): return self._game_number def on_team_join(self, team: ba.SessionTeam) -> None: - team.sessiondata['previous_score'] = team.sessiondata['score'] = 0 + team.customdata['previous_score'] = team.customdata['score'] = 0 def get_max_players(self) -> int: """Return max number of ba.Players allowed to join the game at once.""" @@ -174,7 +174,8 @@ class MultiTeamSession(Session): from bastd.tutorial import TutorialActivity from bastd.activity.multiteamvictory import ( TeamSeriesVictoryScoreScreenActivity) - from ba import _activitytypes + from ba._activitytypes import (TransitionActivity, JoinActivity, + ScoreScreenActivity) # If we have a tutorial to show, that's the first thing we do no # matter what. @@ -186,22 +187,20 @@ class MultiTeamSession(Session): # to transition us into a round gracefully (otherwise we'd snap from # one terrain to another instantly). elif isinstance(activity, TutorialActivity): - self.setactivity( - _ba.new_activity(_activitytypes.TransitionActivity)) + self.setactivity(_ba.new_activity(TransitionActivity)) # If we're in a between-round activity or a restart-activity, hop # into a round. elif isinstance( activity, - (_activitytypes.JoinActivity, _activitytypes.TransitionActivity, - _activitytypes.ScoreScreenActivity)): + (JoinActivity, TransitionActivity, ScoreScreenActivity)): # If we're coming from a series-end activity, reset scores. if isinstance(activity, TeamSeriesVictoryScoreScreenActivity): self.stats.reset() self._game_number = 0 for team in self.teams: - team.sessiondata['score'] = 0 + team.customdata['score'] = 0 # Otherwise just set accum (per-game) scores. else: @@ -221,7 +220,7 @@ class MultiTeamSession(Session): # ..but only ones who have been placed on a team # (ie: no longer sitting in the lobby). try: - has_team = (player.team is not None) + has_team = (player.sessionteam is not None) except NotFoundError: has_team = False if has_team: @@ -263,7 +262,7 @@ class MultiTeamSession(Session): _ba.timer(delay, Call(_ba.playsound, _ba.getsound('boxingBell'))) if announce_winning_team: - winning_team = results.get_winning_team() + winning_team = results.winning_team if winning_team is not None: # Have all players celebrate. celebrate_msg = CelebrateMessage(duration=10.0) diff --git a/assets/src/ba_data/python/ba/_player.py b/assets/src/ba_data/python/ba/_player.py index c889d86a..b986d04c 100644 --- a/assets/src/ba_data/python/ba/_player.py +++ b/assets/src/ba_data/python/ba/_player.py @@ -23,7 +23,7 @@ from __future__ import annotations from dataclasses import dataclass -from typing import TYPE_CHECKING, TypeVar, Generic +from typing import TYPE_CHECKING, TypeVar, Generic, cast import _ba from ba._error import SessionPlayerNotFoundError, print_exception @@ -85,8 +85,7 @@ class Player(Generic[TeamType]): _nodeactor: Optional[ba.NodeActor] _expired: bool _postinited: bool - sessiondata: dict - _gamedata: dict + _customdata: dict # NOTE: avoiding having any __init__() here since it seems to not # get called by default if a dataclass inherits from us. @@ -118,10 +117,9 @@ class Player(Generic[TeamType]): self.character = sessionplayer.character self.color = sessionplayer.color self.highlight = sessionplayer.highlight - self._team = sessionplayer.team.gameteam # type: ignore + self._team = cast(TeamType, sessionplayer.sessionteam.gameteam) assert self._team is not None - self.sessiondata = sessionplayer.sessiondata - self._gamedata = {} + self._customdata = {} self._expired = False self._postinited = True node = _ba.newnode('player', attrs={'playerID': sessionplayer.id}) @@ -144,7 +142,7 @@ class Player(Generic[TeamType]): print_exception(f'Error killing actor on leave for {self}') self._nodeactor = None del self._team - del self._gamedata + del self._customdata def expire(self) -> None: """Called when the Player is expiring (when its Activity does so). @@ -163,7 +161,7 @@ class Player(Generic[TeamType]): self._nodeactor = None self.actor = None del self._team - del self._gamedata + del self._customdata def on_expire(self) -> None: """Can be overridden to handle player expiration. @@ -182,7 +180,7 @@ class Player(Generic[TeamType]): return self._team @property - def gamedata(self) -> dict: + def customdata(self) -> dict: """Arbitrary values associated with the player. Though it is encouraged that most player values be properly defined on the ba.Player subclass, it may be useful for player-agnostic @@ -193,7 +191,7 @@ class Player(Generic[TeamType]): """ assert self._postinited assert not self._expired - return self._gamedata + return self._customdata @property def sessionplayer(self) -> ba.SessionPlayer: diff --git a/assets/src/ba_data/python/ba/_session.py b/assets/src/ba_data/python/ba/_session.py index 2de441ba..98b97728 100644 --- a/assets/src/ba_data/python/ba/_session.py +++ b/assets/src/ba_data/python/ba/_session.py @@ -259,7 +259,7 @@ class Session: else: # Ok, they've already entered the game. Remove them from # teams/activities/etc. - sessionteam = sessionplayer.team + sessionteam = sessionplayer.sessionteam assert sessionteam is not None _ba.screenmessage( @@ -317,7 +317,7 @@ class Session: else: print('Team no in Session teams in on_player_leave.') try: - sessionteam.reset_sessiondata() + sessionteam.leave() except Exception: print_exception(f'Error clearing sessiondata' f' for team {sessionteam} in session {self}.') diff --git a/assets/src/ba_data/python/ba/_stats.py b/assets/src/ba_data/python/ba/_stats.py index 19db090d..cdc0271d 100644 --- a/assets/src/ba_data/python/ba/_stats.py +++ b/assets/src/ba_data/python/ba/_stats.py @@ -74,7 +74,7 @@ class PlayerRecord: self._multi_kill_timer: Optional[ba.Timer] = None self._multi_kill_count = 0 self._stats = weakref.ref(stats) - self._last_player: Optional[ba.SessionPlayer] = None + self._last_sessionplayer: Optional[ba.SessionPlayer] = None self._player: Optional[ba.SessionPlayer] = None self._team: Optional[ReferenceType[ba.SessionTeam]] = None self.streak = 0 @@ -110,7 +110,7 @@ class PlayerRecord: def get_icon(self) -> Dict[str, Any]: """Get the icon for this instance's player.""" - player = self._last_player + player = self._last_sessionplayer assert player is not None return player.get_icon() @@ -127,12 +127,12 @@ class PlayerRecord: return stats.getactivity() return None - def associate_with_player(self, player: ba.SessionPlayer) -> None: - """Associate this entry with a ba.Player.""" - self._team = weakref.ref(player.team) - self.character = player.character - self._last_player = player - self._player = player + def associate_with_player(self, sessionplayer: ba.SessionPlayer) -> None: + """Associate this entry with a ba.SessionPlayer.""" + self._team = weakref.ref(sessionplayer.sessionteam) + self.character = sessionplayer.character + self._last_sessionplayer = sessionplayer + self._player = sessionplayer self.streak = 0 def _end_multi_kill(self) -> None: @@ -141,8 +141,8 @@ class PlayerRecord: def get_last_player(self) -> ba.SessionPlayer: """Return the last ba.Player we were associated with.""" - assert self._last_player is not None - return self._last_player + assert self._last_sessionplayer is not None + return self._last_sessionplayer def submit_kill(self, showpoints: bool = True) -> None: """Submit a kill for this player entry.""" @@ -307,15 +307,14 @@ class Stats: def register_player(self, player: ba.SessionPlayer) -> None: """Register a player with this score-set.""" + assert player.exists() # Invalid refs should never be passed to funcs. name = player.getname() - name_full = player.getname(full=True) - try: + 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) - except Exception: - # FIXME: Shouldn't use top level Exception catch for logic. - # Should only have this as a fallback and always log it. + else: + name_full = player.getname(full=True) self._player_records[name] = PlayerRecord(name, name_full, player, self) @@ -331,11 +330,6 @@ class Stats: records[record_id] = record return records - def player_got_hit(self, player: ba.SessionPlayer) -> None: - """Call this when a player got hit.""" - s_player = self._player_records[player.getname()] - s_player.streak = 0 - def player_scored(self, player: ba.Player, base_points: int = 1, diff --git a/assets/src/ba_data/python/ba/_team.py b/assets/src/ba_data/python/ba/_team.py index b60d294d..fa4be4d6 100644 --- a/assets/src/ba_data/python/ba/_team.py +++ b/assets/src/ba_data/python/ba/_team.py @@ -56,15 +56,10 @@ class SessionTeam: players The list of ba.SessionPlayers on the team. - gamedata - A dict for use by the current ba.Activity - for storing data associated with this team. - This gets cleared for each new ba.Activity. - - sessiondata + customdata A dict for use by the current ba.Session for storing data associated with this team. - Unlike gamedata, this persists for the duration + Unlike customdata, this persists for the duration of the session. """ @@ -72,8 +67,7 @@ class SessionTeam: name: Union[ba.Lstr, str] color: Tuple[float, ...] # FIXME: can't we make this fixed len? players: List[ba.SessionPlayer] - gamedata: Dict - sessiondata: Dict + customdata: dict id: int def __init__(self, @@ -90,12 +84,12 @@ class SessionTeam: self.name = name self.color = tuple(color) self.players = [] - self.sessiondata = {} + self.customdata = {} self.gameteam: Optional[Team] = None - def reset_sessiondata(self) -> None: + def leave(self) -> None: """(internal)""" - self.sessiondata = {} + self.customdata = {} PlayerType = TypeVar('PlayerType', bound='ba.Player') @@ -119,10 +113,7 @@ class Team(Generic[PlayerType]): _sessionteam: ReferenceType[SessionTeam] _expired: bool _postinited: bool - _gamedata: dict - - # TODO: kill these. - sessiondata: dict + _customdata: dict # NOTE: avoiding having any __init__() here since it seems to not # get called by default if a dataclass inherits from us. @@ -149,8 +140,7 @@ class Team(Generic[PlayerType]): self.id = sessionteam.id self.name = sessionteam.name self.color = sessionteam.color - self.sessiondata = sessionteam.sessiondata - self._gamedata = {} + self._customdata = {} self._expired = False self._postinited = True @@ -160,13 +150,12 @@ class Team(Generic[PlayerType]): self.id = team_id self.name = name self.color = color - self._gamedata = {} - self.sessiondata = {} + self._customdata = {} self._expired = False self._postinited = True @property - def gamedata(self) -> dict: + def customdata(self) -> dict: """Arbitrary values associated with the team. Though it is encouraged that most player values be properly defined on the ba.Team subclass, it may be useful for player-agnostic @@ -177,7 +166,7 @@ class Team(Generic[PlayerType]): """ assert self._postinited assert not self._expired - return self._gamedata + return self._customdata def leave(self) -> None: """Called when the Team leaves a running game. @@ -186,7 +175,7 @@ class Team(Generic[PlayerType]): """ assert self._postinited assert not self._expired - del self._gamedata + del self._customdata del self.players def expire(self) -> None: @@ -203,7 +192,7 @@ class Team(Generic[PlayerType]): except Exception: print_exception(f'Error in on_expire for {self}.') - del self._gamedata + del self._customdata del self.players def on_expire(self) -> None: diff --git a/assets/src/ba_data/python/bastd/activity/coopscore.py b/assets/src/ba_data/python/bastd/activity/coopscore.py index e09d8d4c..45d4b0b2 100644 --- a/assets/src/ba_data/python/bastd/activity/coopscore.py +++ b/assets/src/ba_data/python/bastd/activity/coopscore.py @@ -137,9 +137,9 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): self._tournament_time_remaining_text: Optional[Text] = None self._tournament_time_remaining_text_timer: Optional[ba.Timer] = None - self._player_info: List[ba.PlayerInfo] = settings['player_info'] - assert isinstance(self._player_info, list) - assert (isinstance(i, ba.PlayerInfo) for i in self._player_info) + self._playerinfos: List[ba.PlayerInfo] = settings['playerinfos'] + assert isinstance(self._playerinfos, list) + assert (isinstance(i, ba.PlayerInfo) for i in self._playerinfos) self._score: Optional[int] = settings['score'] assert isinstance(self._score, (int, type(None))) @@ -174,7 +174,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): self._game_name_str = self._campaign.name + ':' + self._level_name self._game_config_str = str(len( - self._player_info)) + 'p' + self._campaign.get_level( + self._playerinfos)) + 'p' + self._campaign.get_level( self._level_name).get_score_version_string().replace(' ', '_') # If game-center/etc scores are available we show our friends' @@ -580,12 +580,12 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): ba.timer(5.2, ba.Call(ba.playsound, self._dingsound)) offs_x = -195 - if len(self._player_info) > 1: + if len(self._playerinfos) > 1: pstr = ba.Lstr(value='- ${A} -', subs=[('${A}', ba.Lstr(resource='multiPlayerCountText', subs=[('${COUNT}', - str(len(self._player_info))) + str(len(self._playerinfos))) ]))]) else: pstr = ba.Lstr(value='- ${A} -', @@ -636,7 +636,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): ba.pushcall(ba.WeakCall(self._show_fail)) self._name_str = name_str = ', '.join( - [p.name for p in self._player_info]) + [p.name for p in self._playerinfos]) if self._show_friend_scores: self._friends_loading_status = Text( @@ -662,10 +662,10 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): our_high_scores_all = self._campaign.get_level( self._level_name).get_high_scores() try: - our_high_scores = our_high_scores_all[str(len(self._player_info)) + + our_high_scores = our_high_scores_all[str(len(self._playerinfos)) + ' Player'] except Exception: - our_high_scores = our_high_scores_all[str(len(self._player_info)) + + our_high_scores = our_high_scores_all[str(len(self._playerinfos)) + ' Player'] = [] if self._score is not None: @@ -674,7 +674,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): 'players': [{ 'name': p.name, 'character': p.character - } for p in self._player_info] + } for p in self._playerinfos] } ] our_high_scores.append(our_score) @@ -789,7 +789,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): v_offs_extra = 20 v_offs_names = 0 scale = 1.0 - p_count = len(self._player_info) + p_count = len(self._playerinfos) h_offs_extra -= 75 if p_count > 1: h_offs_extra -= 20 @@ -841,7 +841,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): position=(ts_h_offs + 35 + h_offs_extra, v_offs_extra + ts_height / 2 + -ts_height * (i + 1) / 10 + v_offs_names + v_offs + 11.0), - maxwidth=80.0 + 100.0 * len(self._player_info), + maxwidth=80.0 + 100.0 * len(self._playerinfos), v_align=Text.VAlign.CENTER, color=color1, flash=flash, @@ -1086,7 +1086,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): h_offs_extra = 0 v_offs_names = 0 scale = 1.0 - p_count = len(self._player_info) + p_count = len(self._playerinfos) if p_count > 1: h_offs_extra -= 40 if self._score_type != 'points': @@ -1146,7 +1146,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]): position=(ts_h_offs + 35 + h_offs_extra, ts_height / 2 + -ts_height * (i + 1) / 10 + v_offs_names + v_offs + 11.0), - maxwidth=80.0 + 100.0 * len(self._player_info), + maxwidth=80.0 + 100.0 * len(self._playerinfos), v_align=Text.VAlign.CENTER, color=color1, flash=flash, diff --git a/assets/src/ba_data/python/bastd/activity/dualteamscore.py b/assets/src/ba_data/python/bastd/activity/dualteamscore.py index eb354366..5c7e28cd 100644 --- a/assets/src/ba_data/python/bastd/activity/dualteamscore.py +++ b/assets/src/ba_data/python/bastd/activity/dualteamscore.py @@ -103,7 +103,7 @@ class TeamVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): def _show_team_name(self, pos_v: float, team: ba.SessionTeam, kill_delay: float, shiftdelay: float) -> None: - del kill_delay # unused arg + del kill_delay # Unused arg. ZoomText(ba.Lstr(value='${A}:', subs=[('${A}', team.name)]), position=(100, pos_v), shiftposition=(-150, pos_v), @@ -115,9 +115,9 @@ class TeamVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): color=team.color, jitter=1.0).autoretain() - def _show_team_old_score(self, pos_v: float, team: ba.SessionTeam, + def _show_team_old_score(self, pos_v: float, sessionteam: ba.SessionTeam, shiftdelay: float) -> None: - ZoomText(str(team.sessiondata['score'] - 1), + ZoomText(str(sessionteam.customdata['score'] - 1), position=(150, pos_v), maxwidth=100, color=(0.6, 0.6, 0.7), @@ -129,11 +129,11 @@ class TeamVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): h_align='left', jitter=1.0).autoretain() - def _show_team_score(self, pos_v: float, team: ba.SessionTeam, + def _show_team_score(self, pos_v: float, sessionteam: ba.SessionTeam, scored: bool, kill_delay: float, shiftdelay: float) -> None: - del kill_delay # unused arg - ZoomText(str(team.sessiondata['score']), + del kill_delay # Unused arg. + ZoomText(str(sessionteam.customdata['score']), position=(150, pos_v), maxwidth=100, color=(1.0, 0.9, 0.5) if scored else (0.6, 0.6, 0.7), diff --git a/assets/src/ba_data/python/bastd/activity/freeforallvictory.py b/assets/src/ba_data/python/bastd/activity/freeforallvictory.py index e0bcf462..9e0de4e8 100644 --- a/assets/src/ba_data/python/bastd/activity/freeforallvictory.py +++ b/assets/src/ba_data/python/bastd/activity/freeforallvictory.py @@ -28,7 +28,7 @@ import ba from bastd.activity.multiteamscore import MultiTeamScoreScreenActivity if TYPE_CHECKING: - from typing import Any, Dict, Optional, Set + from typing import Any, Dict, Optional, Set, Tuple class FreeForAllVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): @@ -60,13 +60,17 @@ class FreeForAllVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): player_order_prev = list(self.players) player_order_prev.sort( reverse=True, - key=lambda p: - (p.team.sessiondata['previous_score'], p.getname(full=True))) + key=lambda p: ( + p.team.sessionteam.customdata['previous_score'], + p.getname(full=True), + )) player_order = list(self.players) player_order.sort(reverse=True, - key=lambda p: - (p.team.sessiondata['score'], p.team.sessiondata[ - 'score'], p.getname(full=True))) + key=lambda p: ( + p.team.sessionteam.customdata['score'], + p.team.sessionteam.customdata['score'], + p.getname(full=True), + )) v_offs = -74.0 + spacing * len(player_order_prev) * 0.5 delay1 = 1.3 + 0.1 @@ -202,8 +206,9 @@ class FreeForAllVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): transtime2: ts_h_offs - (95.0 + slide_amt) * scale })) - s_txt = _scoretxt(str(player.team.sessiondata['previous_score']), - 80, 0, False, 0, 1.0) + s_txt = _scoretxt( + str(player.team.sessionteam.customdata['previous_score']), 80, + 0, False, 0, 1.0) ba.timer( tdelay + delay2, ba.WeakCall( @@ -219,8 +224,9 @@ class FreeForAllVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): transtime2: ts_h_offs + (80.0 - slide_amt) * scale })) - score_change = (player.team.sessiondata['score'] - - player.team.sessiondata['previous_score']) + score_change = ( + player.team.sessionteam.customdata['score'] - + player.team.sessionteam.customdata['previous_score']) if score_change > 0: xval = 113 yval = 3.0 @@ -257,12 +263,11 @@ class FreeForAllVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): tdelay + delay1, ba.Call(_safesetattr, s_txt.node, 'color', (1, 1, 1, 1))) for j in range(score_change): - ba.timer( - (tdelay + delay1 + 0.15 * j), - ba.Call( - _safesetattr, s_txt.node, 'text', - str(player.team.sessiondata['previous_score'] + j + - 1))) + ba.timer((tdelay + delay1 + 0.15 * j), + ba.Call( + _safesetattr, s_txt.node, 'text', + str(player.team.sessionteam. + customdata['previous_score'] + j + 1))) tfin = tdelay + delay1 + 0.15 * j if tfin not in sound_times: sound_times.add(tfin) diff --git a/assets/src/ba_data/python/bastd/activity/multiteamscore.py b/assets/src/ba_data/python/bastd/activity/multiteamscore.py index 75bf8e64..77b2a5f9 100644 --- a/assets/src/ba_data/python/bastd/activity/multiteamscore.py +++ b/assets/src/ba_data/python/bastd/activity/multiteamscore.py @@ -121,8 +121,8 @@ class MultiTeamScoreScreenActivity(ScoreScreenActivity): # Results is already sorted; just convert it into a list of # score-set-entries. - for winner in results.get_winners(): - for team in winner.teams: + for winnergroup in results.winnergroups: + for team in winnergroup.teams: if len(team.players) == 1: player_entry = _get_player_score_set_entry( team.players[0]) @@ -172,8 +172,8 @@ class MultiTeamScoreScreenActivity(ScoreScreenActivity): _txt(180, 4, ba.Lstr(resource='killsText')) _txt(280, 4, ba.Lstr(resource='deathsText'), maxwidth=100) - score_name = 'Score' if results is None else results.get_score_name() - translated = ba.Lstr(translate=('scoreNames', score_name)) + score_label = 'Score' if results is None else results.score_label + translated = ba.Lstr(translate=('scoreNames', score_label)) _txt(390, 0, translated) diff --git a/assets/src/ba_data/python/bastd/activity/multiteamvictory.py b/assets/src/ba_data/python/bastd/activity/multiteamvictory.py index 5742bedb..39f37f8c 100644 --- a/assets/src/ba_data/python/bastd/activity/multiteamvictory.py +++ b/assets/src/ba_data/python/bastd/activity/multiteamvictory.py @@ -79,7 +79,7 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): for _pkey, prec in self.stats.get_records().items(): if prec.player.in_game: player_entries.append( - (prec.player.team.sessiondata['score'], + (prec.player.sessionteam.customdata['score'], prec.getname(full=True), prec)) player_entries.sort(reverse=True, key=lambda x: x[0]) else: @@ -145,8 +145,8 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): win_score = (session.get_series_length() - 1) // 2 + 1 lose_score = 0 for team in self.teams: - if team.sessiondata['score'] != win_score: - lose_score = team.sessiondata['score'] + if team.sessionteam.customdata['score'] != win_score: + lose_score = team.sessionteam.customdata['score'] if not self._is_ffa: Text(ba.Lstr(resource='gamesToText', @@ -309,7 +309,7 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity): for _score, name, prec in player_entries: tdelay -= 4 * t_incr v_offs -= 40 - Text(str(prec.team.sessiondata['score']) + Text(str(prec.team.customdata['score']) if self._is_ffa else str(prec.score), color=(0.5, 0.5, 0.5, 1.0), position=(ts_h_offs + 230, ts_height / 2 + v_offs), diff --git a/assets/src/ba_data/python/bastd/actor/respawnicon.py b/assets/src/ba_data/python/bastd/actor/respawnicon.py index 9682bb98..6a7deb5e 100644 --- a/assets/src/ba_data/python/bastd/actor/respawnicon.py +++ b/assets/src/ba_data/python/bastd/actor/respawnicon.py @@ -39,6 +39,9 @@ class RespawnIcon: This is used to indicate that a ba.Player is waiting to respawn. """ + _MASKTEXSTORENAME = ba.storagename('masktex') + _ICONSSTORENAME = ba.storagename('icons') + def __init__(self, player: ba.Player, respawn_time: float): """Instantiate with a ba.Player and respawn_time (in seconds).""" self._visible = True @@ -46,10 +49,10 @@ class RespawnIcon: on_right, offs_extra, respawn_icons = self._get_context(player) # Cache our mask tex on the team for easy access. - mask_tex = player.team.gamedata.get('_spaz_respawn_icons_mask_tex') + mask_tex = player.team.customdata.get(self._MASKTEXSTORENAME) if mask_tex is None: mask_tex = ba.gettexture('characterIconMask') - player.team.gamedata['_spaz_respawn_icons_mask_tex'] = mask_tex + player.team.customdata[self._MASKTEXSTORENAME] = mask_tex assert isinstance(mask_tex, ba.Texture) # Now find the first unused slot and use that. @@ -139,9 +142,9 @@ class RespawnIcon: on_right = player.team.id % 2 == 1 # Store a list of icons in the team. - icons = player.team.gamedata.get('_spaz_respawn_icons') + icons = player.team.customdata.get(self._ICONSSTORENAME) if icons is None: - player.team.gamedata['_spaz_respawn_icons'] = icons = {} + player.team.customdata[self._ICONSSTORENAME] = icons = {} assert isinstance(icons, dict) offs_extra = -20 @@ -149,9 +152,9 @@ class RespawnIcon: on_right = False # Store a list of icons in the activity. - icons = activity.gamedata.get('_spaz_respawn_icons') + icons = activity.customdata.get(self._ICONSSTORENAME) if icons is None: - activity.gamedata['_spaz_respawn_icons'] = icons = {} + activity.customdata[self._ICONSSTORENAME] = icons = {} assert isinstance(icons, dict) if isinstance(activity.session, ba.FreeForAllSession): diff --git a/assets/src/ba_data/python/bastd/actor/scoreboard.py b/assets/src/ba_data/python/bastd/actor/scoreboard.py index 16431102..448cd89a 100644 --- a/assets/src/ba_data/python/bastd/actor/scoreboard.py +++ b/assets/src/ba_data/python/bastd/actor/scoreboard.py @@ -343,6 +343,8 @@ class Scoreboard: category: Gameplay Classes """ + _STORENAME = ba.storagename('entry') + def __init__(self, label: ba.Lstr = None, score_split: float = 0.7): """Instantiate a scoreboard. @@ -382,8 +384,8 @@ class Scoreboard: # Create a proxy in the team which will kill # our entry when it dies (for convenience) - assert '_scoreboard_entry' not in team.gamedata - team.gamedata['_scoreboard_entry'] = _EntryProxy(self, team) + assert self._STORENAME not in team.customdata + team.customdata[self._STORENAME] = _EntryProxy(self, team) # Now set the entry. self._entries[team.id].set_value(score=score, diff --git a/assets/src/ba_data/python/bastd/game/conquest.py b/assets/src/ba_data/python/bastd/game/conquest.py index 744484d9..8ae90f0c 100644 --- a/assets/src/ba_data/python/bastd/game/conquest.py +++ b/assets/src/ba_data/python/bastd/game/conquest.py @@ -61,23 +61,25 @@ class ConquestFlag(Flag): class Player(ba.Player['Team']): """Our player type for this game.""" + # FIXME: We shouldn't be using customdata here + # (but need to update respawn funcs accordingly first). @property def respawn_timer(self) -> Optional[ba.Timer]: """Type safe access to standard respawn timer.""" - return self.gamedata.get('respawn_timer', None) + return self.customdata.get('respawn_timer', None) @respawn_timer.setter def respawn_timer(self, value: Optional[ba.Timer]) -> None: - self.gamedata['respawn_timer'] = value + self.customdata['respawn_timer'] = value @property def respawn_icon(self) -> Optional[RespawnIcon]: """Type safe access to standard respawn icon.""" - return self.gamedata.get('respawn_icon', None) + return self.customdata.get('respawn_icon', None) @respawn_icon.setter def respawn_icon(self, value: Optional[RespawnIcon]) -> None: - self.gamedata['respawn_icon'] = value + self.customdata['respawn_icon'] = value class Team(ba.Team[Player]): diff --git a/assets/src/ba_data/python/bastd/game/easteregghunt.py b/assets/src/ba_data/python/bastd/game/easteregghunt.py index c29301c4..bef86f60 100644 --- a/assets/src/ba_data/python/bastd/game/easteregghunt.py +++ b/assets/src/ba_data/python/bastd/game/easteregghunt.py @@ -203,8 +203,8 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]): # Respawn them shortly. player = msg.getplayer(Player) - assert self.initial_player_info is not None - respawn_time = 2.0 + len(self.initial_player_info) * 1.0 + assert self.initialplayerinfos is not None + respawn_time = 2.0 + len(self.initialplayerinfos) * 1.0 player.respawn_timer = ba.Timer( respawn_time, ba.Call(self.spawn_player_if_exists, player)) player.respawn_icon = RespawnIcon(player, respawn_time) diff --git a/assets/src/ba_data/python/bastd/game/football.py b/assets/src/ba_data/python/bastd/game/football.py index 111ad9e3..e8c03711 100644 --- a/assets/src/ba_data/python/bastd/game/football.py +++ b/assets/src/ba_data/python/bastd/game/football.py @@ -441,7 +441,7 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): if ba.app.kiosk_mode: controlsguide.ControlsGuide(delay=3.0, lifespan=10.0, bright=True).autoretain() - assert self.initial_player_info is not None + assert self.initialplayerinfos is not None abot: Type[SpazBot] bbot: Type[SpazBot] cbot: Type[SpazBot] @@ -450,36 +450,36 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): self._have_tnt = False abot = (BrawlerBotLite if self._preset == 'rookie_easy' else BrawlerBot) - self._bot_types_initial = [abot] * len(self.initial_player_info) + self._bot_types_initial = [abot] * len(self.initialplayerinfos) bbot = (BomberBotLite if self._preset == 'rookie_easy' else BomberBot) self._bot_types_7 = ( - [bbot] * (1 if len(self.initial_player_info) < 3 else 2)) + [bbot] * (1 if len(self.initialplayerinfos) < 3 else 2)) cbot = (BomberBot if self._preset == 'rookie_easy' else TriggerBot) self._bot_types_14 = ( - [cbot] * (1 if len(self.initial_player_info) < 3 else 2)) + [cbot] * (1 if len(self.initialplayerinfos) < 3 else 2)) elif self._preset == 'tournament': self._exclude_powerups = [] self._have_tnt = True self._bot_types_initial = ( - [BrawlerBot] * (1 if len(self.initial_player_info) < 2 else 2)) + [BrawlerBot] * (1 if len(self.initialplayerinfos) < 2 else 2)) self._bot_types_7 = ( - [TriggerBot] * (1 if len(self.initial_player_info) < 3 else 2)) + [TriggerBot] * (1 if len(self.initialplayerinfos) < 3 else 2)) self._bot_types_14 = ( - [ChargerBot] * (1 if len(self.initial_player_info) < 4 else 2)) + [ChargerBot] * (1 if len(self.initialplayerinfos) < 4 else 2)) elif self._preset in ['pro', 'pro_easy', 'tournament_pro']: self._exclude_powerups = ['curse'] self._have_tnt = True self._bot_types_initial = [ChargerBot] * len( - self.initial_player_info) + self.initialplayerinfos) abot = (BrawlerBot if self._preset == 'pro' else BrawlerBotLite) typed_bot_list: List[Type[SpazBot]] = [] self._bot_types_7 = ( typed_bot_list + [abot] + [BomberBot] * - (1 if len(self.initial_player_info) < 3 else 2)) + (1 if len(self.initialplayerinfos) < 3 else 2)) bbot = (TriggerBotPro if self._preset == 'pro' else TriggerBot) self._bot_types_14 = ( - [bbot] * (1 if len(self.initial_player_info) < 3 else 2)) + [bbot] * (1 if len(self.initialplayerinfos) < 3 else 2)) elif self._preset in ['uber', 'uber_easy']: self._exclude_powerups = [] self._have_tnt = True @@ -487,12 +487,11 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): bbot = (TriggerBotPro if self._preset == 'uber' else TriggerBot) typed_bot_list_2: List[Type[SpazBot]] = [] self._bot_types_initial = (typed_bot_list_2 + [StickyBot] + - [abot] * len(self.initial_player_info)) + [abot] * len(self.initialplayerinfos)) self._bot_types_7 = ( - [bbot] * (1 if len(self.initial_player_info) < 3 else 2)) + [bbot] * (1 if len(self.initialplayerinfos) < 3 else 2)) self._bot_types_14 = ( - [ExplodeyBot] * - (1 if len(self.initial_player_info) < 3 else 2)) + [ExplodeyBot] * (1 if len(self.initialplayerinfos) < 3 else 2)) else: raise Exception() @@ -794,7 +793,7 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): 'outcome': outcome, 'score': scoreval, 'score_order': 'decreasing', - 'player_info': self.initial_player_info + 'playerinfos': self.initialplayerinfos }) def handlemessage(self, msg: Any) -> Any: @@ -805,8 +804,8 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]): # Respawn them shortly. player = msg.getplayer(Player) - assert self.initial_player_info is not None - respawn_time = 2.0 + len(self.initial_player_info) * 1.0 + assert self.initialplayerinfos is not None + respawn_time = 2.0 + len(self.initialplayerinfos) * 1.0 player.respawn_timer = ba.Timer( respawn_time, ba.Call(self.spawn_player_if_exists, player)) player.respawn_icon = RespawnIcon(player, respawn_time) diff --git a/assets/src/ba_data/python/bastd/game/kingofthehill.py b/assets/src/ba_data/python/bastd/game/kingofthehill.py index aa940744..56c04457 100644 --- a/assets/src/ba_data/python/bastd/game/kingofthehill.py +++ b/assets/src/ba_data/python/bastd/game/kingofthehill.py @@ -154,7 +154,6 @@ class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]): ba.timer(1.0, self._tick, repeat=True) self._flag_state = FlagState.NEW Flag.project_stand(self._flag_pos) - self._flag = Flag(position=self._flag_pos, touchable=False, color=(1, 1, 1)) diff --git a/assets/src/ba_data/python/bastd/game/ninjafight.py b/assets/src/ba_data/python/bastd/game/ninjafight.py index 42e73e51..72e15581 100644 --- a/assets/src/ba_data/python/bastd/game/ninjafight.py +++ b/assets/src/ba_data/python/bastd/game/ninjafight.py @@ -113,12 +113,12 @@ class NinjaFightGame(ba.TeamGameActivity[Player, Team]): ChargerBot, pos=(-5, 3, -2), spawn_time=3.0)) # Add some extras for multiplayer or pro mode. - assert self.initial_player_info is not None - if len(self.initial_player_info) > 2 or is_pro: + assert self.initialplayerinfos is not None + if len(self.initialplayerinfos) > 2 or is_pro: ba.timer( 5.0, lambda: self._bots.spawn_bot( ChargerBot, pos=(0, 3, -5), spawn_time=3.0)) - if len(self.initial_player_info) > 3 or is_pro: + if len(self.initialplayerinfos) > 3 or is_pro: ba.timer( 6.0, lambda: self._bots.spawn_bot( ChargerBot, pos=(0, 3, 1), spawn_time=3.0)) diff --git a/assets/src/ba_data/python/bastd/game/onslaught.py b/assets/src/ba_data/python/bastd/game/onslaught.py index e08d0d78..430a7251 100644 --- a/assets/src/ba_data/python/bastd/game/onslaught.py +++ b/assets/src/ba_data/python/bastd/game/onslaught.py @@ -757,7 +757,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): 'outcome': outcome, 'score': score, 'fail_message': fail_message, - 'player_info': self.initial_player_info + 'playerinfos': self.initialplayerinfos }, delay=delay) @@ -844,10 +844,10 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]): for player in self.players: try: if player.is_alive(): - assert self.initial_player_info is not None + assert self.initialplayerinfos is not None self.stats.player_scored( player, - int(100 / len(self.initial_player_info)), + int(100 / len(self.initialplayerinfos)), scale=1.4, color=(0.6, 0.6, 1.0, 1.0), title=ba.Lstr(resource='completionBonusText'), diff --git a/assets/src/ba_data/python/bastd/game/runaround.py b/assets/src/ba_data/python/bastd/game/runaround.py index c303f557..d4b1b6cb 100644 --- a/assets/src/ba_data/python/bastd/game/runaround.py +++ b/assets/src/ba_data/python/bastd/game/runaround.py @@ -563,7 +563,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): 'outcome': outcome, 'score': score, 'fail_message': fail_message, - 'player_info': self.initial_player_info + 'playerinfos': self.initialplayerinfos }) def _on_got_scores_to_beat(self, scores: List[Dict[str, Any]]) -> None: @@ -1118,8 +1118,8 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]): # Respawn them shortly. player = msg.getplayer(Player) - assert self.initial_player_info is not None - respawn_time = 2.0 + len(self.initial_player_info) * 1.0 + assert self.initialplayerinfos is not None + respawn_time = 2.0 + len(self.initialplayerinfos) * 1.0 player.respawn_timer = ba.Timer( respawn_time, ba.Call(self.spawn_player_if_exists, player)) player.respawn_icon = RespawnIcon(player, respawn_time) diff --git a/assets/src/ba_data/python/bastd/game/thelaststand.py b/assets/src/ba_data/python/bastd/game/thelaststand.py index 20c4b626..77c74e81 100644 --- a/assets/src/ba_data/python/bastd/game/thelaststand.py +++ b/assets/src/ba_data/python/bastd/game/thelaststand.py @@ -196,7 +196,7 @@ class TheLastStandGame(ba.CoopGameActivity[Player, Team]): results={ 'outcome': outcome, 'score': self._score, - 'player_info': self.initial_player_info + 'playerinfos': self.initialplayerinfos }) def _update_bots(self) -> None: diff --git a/docs/ba_module.md b/docs/ba_module.md index 34fb6596..af2783a2 100644 --- a/docs/ba_module.md +++ b/docs/ba_module.md @@ -1,5 +1,5 @@ -

last updated on 2020-06-01 for Ballistica version 1.5.0 build 20039

+

last updated on 2020-06-02 for Ballistica version 1.5.0 build 20041

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!


@@ -92,6 +92,7 @@
  • ba.screenmessage()
  • ba.set_analytics_screen()
  • ba.setlanguage()
  • +
  • ba.storagename()
  • ba.time()
  • ba.timer()
  • ba.timestring()
  • @@ -343,8 +344,16 @@ actually award achievements.

    can overlap during transitions.

    Attributes:

    -
    expired, gamedata, globalsnode, players, playertype, session, settings_raw, stats, teams, teamtype
    +
    customdata, expired, globalsnode, players, playertype, session, settings_raw, stats, teams, teamtype
    +

    customdata

    +

    dict

    +

    Entities needing to store simple data with an activity can put it + here. This dict will be deleted when the activity expires, so contained + objects generally do not need to worry about handling expired + activities.

    + +

    expired

    bool

    Whether the activity is expired.

    @@ -353,14 +362,6 @@ actually award achievements.

    At this point no new nodes, timers, etc should be made, run, etc, and the activity should be considered a 'zombie'.

    -
    -

    gamedata

    -

    dict

    -

    Entities needing to store simple data with an activity can put it - here. This dict will be deleted when the activity expires, so contained - objects generally do not need to worry about handling expired - activities.

    -

    globalsnode

    ba.Node

    @@ -1552,8 +1553,16 @@ start_long_action(callback_when_done=ba.ContextC

    Attributes Inherited:

    players, settings_raw, teams

    Attributes Defined Here:

    -
    expired, gamedata, globalsnode, map, playertype, session, stats, teamtype
    +
    customdata, expired, globalsnode, map, playertype, session, stats, teamtype
    +

    customdata

    +

    dict

    +

    Entities needing to store simple data with an activity can put it + here. This dict will be deleted when the activity expires, so contained + objects generally do not need to worry about handling expired + activities.

    + +

    expired

    bool

    Whether the activity is expired.

    @@ -1562,14 +1571,6 @@ start_long_action(callback_when_done=ba.ContextC At this point no new nodes, timers, etc should be made, run, etc, and the activity should be considered a 'zombie'.

    -
    -

    gamedata

    -

    dict

    -

    Entities needing to store simple data with an activity can put it - here. This dict will be deleted when the activity expires, so contained - objects generally do not need to worry about handling expired - activities.

    -

    globalsnode

    ba.Node

    @@ -2145,8 +2146,16 @@ its time with lingering corpses, sound effects, etc.

    Attributes Inherited:

    players, settings_raw, teams

    Attributes Defined Here:

    -
    expired, gamedata, globalsnode, map, playertype, session, stats, teamtype
    +
    customdata, expired, globalsnode, map, playertype, session, stats, teamtype
    +

    customdata

    +

    dict

    +

    Entities needing to store simple data with an activity can put it + here. This dict will be deleted when the activity expires, so contained + objects generally do not need to worry about handling expired + activities.

    + +

    expired

    bool

    Whether the activity is expired.

    @@ -2155,14 +2164,6 @@ its time with lingering corpses, sound effects, etc.

    At this point no new nodes, timers, etc should be made, run, etc, and the activity should be considered a 'zombie'.

    -
    -

    gamedata

    -

    dict

    -

    Entities needing to store simple data with an activity can put it - here. This dict will be deleted when the activity expires, so contained - objects generally do not need to worry about handling expired - activities.

    -

    globalsnode

    ba.Node

    @@ -3871,14 +3872,14 @@ even if myactor is set to None.

    own custom ba.Player types.

    Attributes:

    -
    actor, gamedata, node, position, sessionplayer, team
    +
    actor, customdata, node, position, sessionplayer, team

    actor

    Optional[ba.Actor]

    The ba.Actor associated with the player.

    -

    gamedata

    +

    customdata

    dict

    Arbitrary values associated with the player. Though it is encouraged that most player values be properly defined @@ -4111,9 +4112,9 @@ the type-checker properly identifies the returned value as one.

    associate_with_player()

    -

    associate_with_player(self, player: ba.SessionPlayer) -> None

    +

    associate_with_player(self, sessionplayer: ba.SessionPlayer) -> None

    -

    Associate this entry with a ba.Player.

    +

    Associate this entry with a ba.SessionPlayer.

    cancel_multi_kill_timer()

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

    Attributes:

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

    character

    str

    @@ -4602,14 +4603,7 @@ any lobby character/team selection.

    The input device associated with the player.

    -

    sessiondata

    -

    Dict

    -

    A dict for use by the current ba.Session for -storing data associated with this player. -This persists for the duration of the session.

    - -
    -

    team

    +

    sessionteam

    ba.SessionTeam

    The ba.SessionTeam this Player is on. If the SessionPlayer is still in its lobby selecting a team/etc. then a @@ -4708,18 +4702,19 @@ other players.

    each SessionTeam consists of just one SessionPlayer.

    Attributes:

    -
    color, gamedata, id, name, players, sessiondata
    +
    color, customdata, id, name, players

    color

    Tuple[float, ...]

    The team's color.

    -

    gamedata

    -

    Dict

    -

    A dict for use by the current ba.Activity -for storing data associated with this team. -This gets cleared for each new ba.Activity.

    +

    customdata

    +

    dict

    +

    A dict for use by the current ba.Session for +storing data associated with this team. +Unlike customdata, this persists for the duration +of the session.

    id

    @@ -4736,14 +4731,6 @@ This gets cleared for each new ba.Activity.

    List[ba.SessionPlayer]

    The list of ba.SessionPlayers on the team.

    -
    -

    sessiondata

    -

    Dict

    -

    A dict for use by the current ba.Session for -storing data associated with this team. -Unlike gamedata, this persists for the duration -of the session.

    -

    Methods:

    @@ -4952,7 +4939,7 @@ of the session.

    Methods:

    -
    <constructor>, get_records(), getactivity(), player_got_hit(), player_scored(), player_was_killed(), register_player(), reset(), reset_accum(), setactivity()
    +
    <constructor>, get_records(), getactivity(), player_scored(), player_was_killed(), register_player(), reset(), reset_accum(), setactivity()

    <constructor>

    ba.Stats()

    @@ -4971,12 +4958,6 @@ of the session.

    May return None.

    -
    -

    player_got_hit()

    -

    player_got_hit(self, player: ba.SessionPlayer) -> None

    - -

    Call this when a player got hit.

    -

    player_scored()

    player_scored(self, player: ba.Player, base_points: int = 1, target: Sequence[float] = None, kill: bool = False, victim_player: ba.Player = None, scale: float = 1.0, color: Sequence[float] = None, title: Union[str, ba.Lstr] = None, screenmessage: bool = True, display: bool = True, importance: int = 1, showpoints: bool = True, big_message: bool = False) -> int

    @@ -5029,9 +5010,9 @@ of the session.

    Attributes:

    -
    gamedata, sessionteam
    +
    customdata, sessionteam
    -

    gamedata

    +

    customdata

    dict

    Arbitrary values associated with the team. Though it is encouraged that most player values be properly defined @@ -5080,8 +5061,16 @@ of the session.

    Attributes Inherited:

    players, settings_raw, teams

    Attributes Defined Here:

    -
    expired, gamedata, globalsnode, map, playertype, session, stats, teamtype
    +
    customdata, expired, globalsnode, map, playertype, session, stats, teamtype
    +

    customdata

    +

    dict

    +

    Entities needing to store simple data with an activity can put it + here. This dict will be deleted when the activity expires, so contained + objects generally do not need to worry about handling expired + activities.

    + +

    expired

    bool

    Whether the activity is expired.

    @@ -5090,14 +5079,6 @@ of the session.

    At this point no new nodes, timers, etc should be made, run, etc, and the activity should be considered a 'zombie'.

    -
    -

    gamedata

    -

    dict

    -

    Entities needing to store simple data with an activity can put it - here. This dict will be deleted when the activity expires, so contained - objects generally do not need to worry about handling expired - activities.

    -

    globalsnode

    ba.Node

    @@ -5208,43 +5189,58 @@ Results for a completed ba.TeamGameActivity

    Upon completion, a game should fill one of these out and pass it to its ba.Activity.end() call.

    +

    Attributes:

    +
    lower_is_better, playerinfos, score_label, scoretype, sessionteams, winnergroups, winning_team
    +
    +

    lower_is_better

    +

    bool

    +

    Whether lower scores are better.

    + +
    +

    playerinfos

    +

    List[ba.PlayerInfo]

    +

    Get info about the players represented by the results.

    + +
    +

    score_label

    +

    str

    +

    The label associated with scores ('points', etc).

    + +
    +

    scoretype

    +

    ba.ScoreType

    +

    The type of score.

    + +
    +

    sessionteams

    +

    List[ba.SessionTeam]

    +

    Return all ba.SessionTeams in the results.

    + +
    +

    winnergroups

    +

    List[WinnerGroup]

    +

    Get an ordered list of winner groups.

    + +
    +

    winning_team

    +

    Optional[ba.SessionTeam]

    +

    The winning ba.SessionTeam if there is exactly one, or else None.

    + +
    +

    Methods:

    -
    <constructor>, get_lower_is_better(), get_player_info(), get_score_name(), get_score_type(), get_team_score(), get_team_score_str(), get_teams(), get_winners(), get_winning_team(), has_score_for_team(), set_game(), set_team_score()
    +
    <constructor>, get_team_score(), get_team_score_str(), has_score_for_team(), set_game(), set_team_score()

    <constructor>

    ba.TeamGameResults()

    Instantiate a results instance.

    -
    -

    get_lower_is_better()

    -

    get_lower_is_better(self) -> bool

    - -

    Return whether lower scores are better.

    - -
    -

    get_player_info()

    -

    get_player_info(self) -> List[ba.PlayerInfo]

    - -

    Get info about the players represented by the results.

    - -
    -

    get_score_name()

    -

    get_score_name(self) -> str

    - -

    Get the name associated with scores ('points', etc).

    - -
    -

    get_score_type()

    -

    get_score_type(self) -> ba.ScoreType

    - -

    Get the type of score.

    -

    get_team_score()

    -

    get_team_score(self, team: Union[ba.SessionTeam, ba.Team]) -> Optional[int]

    +

    get_team_score(self, sessionteam: Union[ba.SessionTeam]) -> Optional[int]

    -

    Return the score for a given team.

    +

    Return the score for a given ba.SessionTeam.

    get_team_score_str()

    @@ -5254,24 +5250,6 @@ Results for a completed ba.TeamGameActivity

    (properly formatted for the score type.)

    -
    -

    get_teams()

    -

    get_teams(self) -> List[ba.SessionTeam]

    - -

    Return all ba.SessionTeams in the results.

    - -
    -

    get_winners()

    -

    get_winners(self) -> List[WinnerGroup]

    - -

    Get an ordered list of winner groups.

    - -
    -

    get_winning_team()

    -

    get_winning_team(self) -> Optional[ba.SessionTeam]

    - -

    Get the winning ba.Team if there is exactly one; None otherwise.

    -

    has_score_for_team()

    has_score_for_team(self, sessionteam: ba.SessionTeam) -> bool

    @@ -5286,7 +5264,7 @@ Results for a completed ba.TeamGameActivity

    set_team_score()

    -

    set_team_score(self, team: Union[ba.SessionTeam, ba.Team], score: Optional[int]) -> None

    +

    set_team_score(self, team: ba.Team, score: Optional[int]) -> None

    Set the score for a given ba.Team.

    @@ -6422,6 +6400,30 @@ playing, the playing track will not be restarted.

    Category: Gameplay Functions

    +
    +

    ba.storagename()

    +

    storagename(basename: str) -> str

    + +

    Generate a (hopefully) unique name for storing things in public places.

    + +

    Category: General Utility Functions

    + +

    This consists of a leading underscore, the module path at the +call site with dots replaced by underscores, the class name, and +the provided suffix. When storing data in public places such as +'customdata' dicts, this minimizes the chance of collisions if a +module or class is duplicated or renamed.

    + +
    # Example: generate a unique name for storage purposes:
    +class MyThingie:
    + +
        # This will give something like '_mymodule_submodule_mythingie_data'.
    +    _STORENAME = ba.storagename('data')
    + +

    def __init__(self, activity): + # Store some data in the Activity we were passed + activity.customdata[self._STORENAME] = {}

    +

    ba.textwidget()

    textwidget(edit: Widget = None, parent: Widget = None,